From 21626502bb2d67c775715a74f265a8f2776a7fa9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Sep 2018 20:21:12 -0700 Subject: [PATCH] Switch: Add audio, key mapping, fast-forward, bugfixes --- res/font-new.png | Bin 15466 -> 15473 bytes src/platform/switch/gui-font.c | 62 ++++++++++++++--- src/platform/switch/main.c | 119 +++++++++++++++++++++++++++++++-- src/util/gui/menu.c | 2 +- 4 files changed, 168 insertions(+), 15 deletions(-) diff --git a/res/font-new.png b/res/font-new.png index e08b5bccbd294cc63276c2f5ab35d2048f08dbae..1e7876a33777d8f0ffd87d20d1336468830b9b6e 100644 GIT binary patch literal 15473 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

MauvE%$m)wP=a-y=TEnM9DkkaJynDrV>M)I6Wb> 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 Ss{B1m{9a04vgob8-~R!Cd-I6^ literal 15466 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 Sv+LhY#L92q%U8ZJ5B*=TEv{_< diff --git a/src/platform/switch/gui-font.c b/src/platform/switch/gui-font.c index 382de6db0..1f28b3e1e 100644 --- a/src/platform/switch/gui-font.c +++ b/src/platform/switch/gui-font.c @@ -31,12 +31,13 @@ static const char* const _vertexShader = "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 @@ static const char* const _fragmentShader = "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 @@ struct GUIFont { GLuint offsetLocation; GLuint texLocation; GLuint dimsLocation; + GLuint transformLocation; GLuint colorLocation; GLuint originLocation; GLuint glyphLocation; @@ -159,6 +162,7 @@ struct GUIFont* GUIFontCreate(void) { 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 @@ void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, 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 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment 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 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment 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 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment 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 @@ void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, 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); } diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index e0c9ceadb..4fd4e7881 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -4,7 +4,9 @@ * 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 #include +#include #include #include #include @@ -14,6 +16,11 @@ #include #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 @@ static const char* const _vertexShader = "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 @@ static bool initEgl() { 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 @@ static void _drawStart(void) { } 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 @@ static void _setup(struct mGUIRunner* runner) { _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 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { 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 uint16_t _pollGameInput(struct mGUIRunner* runner) { 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 @@ int main(int argc, char* argv[]) { 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 @@ int main(int argc, char* argv[]) { 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 @@ int main(int argc, char* argv[]) { _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; diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 763826c4b..f9cba3d36 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -176,8 +176,8 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men 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);