all repos — mgba @ 21626502bb2d67c775715a74f265a8f2776a7fa9

mGBA Game Boy Advance Emulator

Switch: Add audio, key mapping, fast-forward, bugfixes
Vicki Pfau vi@endrift.com
Fri, 14 Sep 2018 20:21:12 -0700
commit

21626502bb2d67c775715a74f265a8f2776a7fa9

parent

af03ad75be05379bbecbc326e849d26d25237652

4 files changed, 168 insertions(+), 15 deletions(-)

jump to
M src/platform/switch/gui-font.csrc/platform/switch/gui-font.c

@@ -31,12 +31,13 @@ "attribute vec2 offset;\n"

"uniform vec3 origin;\n" "uniform vec2 glyph;\n" "uniform vec2 dims;\n" + "uniform mat2 transform;\n" "varying vec2 texCoord;\n" "void main() {\n" - " vec2 scaledOffset = offset * dims;\n" - " gl_Position = vec4((origin.x + scaledOffset.x) / 640.0 - 1.0, -(origin.y + scaledOffset.y) / 384.0 + 1.0, origin.z, 1.0);\n" - " texCoord = (glyph + scaledOffset) / 512.0;\n" + " texCoord = (glyph + offset * dims) / 512.0;\n" + " vec2 scaledOffset = (transform * (offset * 2.0 - vec2(1.0)) + vec2(1.0)) / 2.0 * dims;\n" + " gl_Position = vec4((origin.x + scaledOffset.x) / 640.0 - 1.0, -(origin.y + scaledOffset.y) / 360.0 + 1.0, origin.z, 1.0);\n" "}"; static const char* const _fragmentShader =

@@ -46,9 +47,10 @@ "uniform vec4 color;\n"

"uniform float cutoff;\n" "void main() {\n" - " vec4 texColor = color;\n" - " texColor.a *= texture2D(tex, texCoord).a;\n" - " texColor.a = clamp((texColor.a - cutoff) / (1.0 - cutoff), 0.0, 1.0);\n" + " vec4 texColor = texture2D(tex, texCoord);\n" + " texColor.a = clamp((texColor.a - cutoff) / (1.0 - cutoff), 0.0, 1.0);\n" + " texColor.rgb = color.rgb;\n" + " texColor.a *= color.a;\n" " gl_FragColor = texColor;\n" "}";

@@ -59,6 +61,7 @@ GLuint vbo;

GLuint offsetLocation; GLuint texLocation; GLuint dimsLocation; + GLuint transformLocation; GLuint colorLocation; GLuint originLocation; GLuint glyphLocation;

@@ -159,6 +162,7 @@

font->texLocation = glGetUniformLocation(font->program, "tex"); font->colorLocation = glGetUniformLocation(font->program, "color"); font->dimsLocation = glGetUniformLocation(font->program, "dims"); + font->transformLocation = glGetUniformLocation(font->program, "transform"); font->originLocation = glGetUniformLocation(font->program, "origin"); font->glyphLocation = glGetUniformLocation(font->program, "glyph"); font->cutoffLocation = glGetUniformLocation(font->program, "cutoff");

@@ -229,6 +233,7 @@ glUniform1i(font->texLocation, 0);

glUniform2f(font->glyphLocation, (glyph & 15) * CELL_WIDTH + metric.padding.left * 2, (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2); glUniform2f(font->dimsLocation, CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2, CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2); glUniform3f(font->originLocation, x, y - GLYPH_HEIGHT + metric.padding.top * 2, 0); + glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {1.0, 0.0, 0.0, 1.0}); glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(font->offsetLocation);

@@ -251,6 +256,9 @@ if (icon >= GUI_ICON_MAX) {

return; } struct GUIIconMetric metric = defaultIconMetrics[icon]; + + float hFlip = 1.0f; + float vFlip = 1.0f; switch (align & GUI_ALIGN_HCENTER) { case GUI_ALIGN_HCENTER: x -= metric.width;

@@ -271,10 +279,10 @@

glUseProgram(font->program); switch (orient) { case GUI_ORIENT_HMIRROR: - // TODO + hFlip = -1.0; break; case GUI_ORIENT_VMIRROR: - // TODO + vFlip = -1.0; break; case GUI_ORIENT_0: default:

@@ -294,6 +302,7 @@ glUniform1i(font->texLocation, 0);

glUniform2f(font->glyphLocation, metric.x * 2, metric.y * 2 + 256); glUniform2f(font->dimsLocation, metric.width * 2, metric.height * 2); glUniform3f(font->originLocation, x, y, 0); + glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {hFlip, 0.0, 0.0, vFlip}); glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(font->offsetLocation);

@@ -316,5 +325,40 @@ if (icon >= GUI_ICON_MAX) {

return; } struct GUIIconMetric metric = defaultIconMetrics[icon]; - // + + if (!w) { + w = metric.width * 2; + } + if (!h) { + h = metric.height * 2; + } + + glUseProgram(font->program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, font->font); + glBindBuffer(GL_ARRAY_BUFFER, font->vbo); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUniform1i(font->texLocation, 0); + glUniform2f(font->glyphLocation, metric.x * 2, metric.y * 2 + 256); + glUniform2f(font->dimsLocation, metric.width * 2, metric.height * 2); + glUniform3f(font->originLocation, x + w / 2 - metric.width, y + h / 2 - metric.height, 0); + glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {w * 0.5f / metric.width, 0.0, 0.0, h * 0.5f / metric.height}); + + glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray(font->offsetLocation); + + glUniform1f(font->cutoffLocation, 0.1f); + glUniform4f(font->colorLocation, 0.0, 0.0, 0.0, ((color >> 24) & 0xFF) / 128.0f); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glUniform1f(font->cutoffLocation, 0.7f); + glUniform4f(font->colorLocation, ((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glDisableVertexAttribArray(font->offsetLocation); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); }
M src/platform/switch/main.csrc/platform/switch/main.c

@@ -4,7 +4,9 @@ * This Source Code Form is subject to the terms of the Mozilla Public

* License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "feature/gui/gui-runner.h" +#include <mgba/core/blip_buf.h> #include <mgba/core/core.h> +#include <mgba/internal/gba/audio.h> #include <mgba/internal/gba/input.h> #include <mgba-util/gui.h> #include <mgba-util/gui/font.h>

@@ -14,6 +16,11 @@ #include <EGL/egl.h>

#include <GLES2/gl2.h> #define AUTO_INPUT 0x4E585031 +#define SAMPLES 0x400 +#define BUFFER_SIZE 0x1000 +#define N_BUFFERS 3 + +TimeType __nx_time_type = TimeType_UserSystemClock; static EGLDisplay s_display; static EGLContext s_context;

@@ -38,7 +45,7 @@ "varying vec2 texCoord;\n"

"void main() {\n" " vec2 ratio = insize / 256.0;\n" - " vec2 scaledOffset = offset * dims;\n" + " vec2 scaledOffset = offset * dims;\n" " gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n" " texCoord = offset * ratio;\n" "}";

@@ -64,6 +71,13 @@ static GLuint colorLocation;

static GLuint tex; static color_t frameBuffer[256 * 256]; +static struct mAVStream stream; +static int audioBufferActive; +static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000))); +static AudioOutBuffer audoutBuffer[N_BUFFERS]; +static int enqueuedBuffers; +static bool frameLimiter = true; +static int framecount = 0; static bool initEgl() { s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

@@ -91,6 +105,10 @@ if (!s_surface) {

goto _fail1; } + //EGLint contextAttributeList[] = { + // EGL_CONTEXT_CLIENT_VERSION, 2, + // EGL_NONE + //}; s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, NULL); if (!s_context) { goto _fail2;

@@ -130,7 +148,9 @@ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

} static void _drawEnd(void) { - eglSwapBuffers(s_display, s_surface); + if (frameLimiter || (framecount & 2) == 0) { + eglSwapBuffers(s_display, s_surface); + } } static uint32_t _pollInput(const struct mInputMap* map) {

@@ -154,6 +174,15 @@ _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);

_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R); runner->core->setVideoBuffer(runner->core, frameBuffer, 256); + runner->core->setAVStream(runner->core, &stream); +} + +static void _gameLoaded(struct mGUIRunner* runner) { + u32 samplerate = audoutGetSampleRate(); + + double ratio = GBAAudioCalculateRatio(1, 60.0, 1); + blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio); + blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio); } static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {

@@ -202,6 +231,8 @@

unsigned width, height; runner->core->desiredVideoDimensions(runner->core, &width, &height); _drawTex(runner, width, height, faded); + + ++framecount; } static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {

@@ -222,18 +253,41 @@ }

static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) { UNUSED(runner); -} + frameLimiter = limit; +} static bool _running(struct mGUIRunner* runner) { UNUSED(runner); return appletMainLoop(); } +static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { + UNUSED(stream); + static AudioOutBuffer* releasedBuffers; + u32 audoutNReleasedBuffers; + audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers); + enqueuedBuffers -= audoutNReleasedBuffers; + if (!frameLimiter && enqueuedBuffers == N_BUFFERS) { + blip_clear(left); + blip_clear(right); + return; + } + + struct GBAStereoSample* samples = audioBuffer[audioBufferActive]; + blip_read_samples(left, &samples[0].left, SAMPLES, true); + blip_read_samples(right, &samples[0].right, SAMPLES, true); + audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]); + audioBufferActive += 1; + audioBufferActive %= N_BUFFERS; + ++enqueuedBuffers; +} + int main(int argc, char* argv[]) { socketInitializeDefault(); nxlinkStdio(); initEgl(); romfsInit(); + audoutInitialize(); struct GUIFont* font = GUIFontCreate();

@@ -301,6 +355,23 @@ glBindBuffer(GL_ARRAY_BUFFER, vbo);

glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); + stream.videoDimensionsChanged = NULL; + stream.postVideoFrame = NULL; + stream.postAudioFrame = NULL; + stream.postAudioBuffer = _postAudioBuffer; + + memset(audioBuffer, 0, sizeof(audioBuffer)); + audioBufferActive = 0; + enqueuedBuffers = 0; + size_t i; + for (i = 0; i < N_BUFFERS; ++i) { + audoutBuffer[i].next = NULL; + audoutBuffer[i].buffer = audioBuffer[i]; + audoutBuffer[i].buffer_size = BUFFER_SIZE; + audoutBuffer[i].data_size = BUFFER_SIZE; + audoutBuffer[i].data_offset = 0; + } + struct mGUIRunner runner = { .params = { width, height,

@@ -310,16 +381,52 @@ _pollInput, NULL,

NULL, NULL, NULL, }, + .keySources = (struct GUIInputKeys[]) { + { + .name = "Controller Input", + .id = AUTO_INPUT, + .keyNames = (const char*[]) { + "A", + "B", + "X", + "Y", + "L Stick", + "R Stick", + "L", + "R", + "ZL", + "ZR", + "+", + "-", + "Left", + "Up", + "Right", + "Down", + "L Left", + "L Up", + "L Right", + "L Down", + "R Left", + "R Up", + "R Right", + "R Down", + "SL", + "SR" + }, + .nKeys = 26 + }, + { .id = 0 } + }, .nConfigExtra = 0, .setup = _setup, .teardown = NULL, - .gameLoaded = NULL, + .gameLoaded = _gameLoaded, .gameUnloaded = NULL, .prepareForFrame = NULL, .drawFrame = _drawFrame, .drawScreenshot = _drawScreenshot, .paused = NULL, - .unpaused = NULL, + .unpaused = _gameLoaded, .incrementScreenMode = NULL, .setFrameLimiter = _setFrameLimiter, .pollGameInput = _pollGameInput,

@@ -335,8 +442,10 @@ _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);

_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT); _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT); + audoutStartAudioOut(); mGUIRunloop(&runner); + audoutExit(); deinitEgl(); socketExit(); return 0;
M src/util/gui/menu.csrc/util/gui/menu.c

@@ -176,8 +176,8 @@ unsigned right;

GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0); GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0); right = (right - w) / 2; + GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xA0FFFFFF, GUI_ICON_SCROLLBAR_TRACK); GUIFontDrawIcon(params->font, params->width - 8, top, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); - GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_TRACK); GUIFontDrawIcon(params->font, params->width - 8, bottom, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); y = menu->index * (bottom - top - 16) / GUIMenuItemListSize(&menu->items);