From e928c451eb1fd41d772af907430e29e657451a98 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 17:13:03 -0700 Subject: [PATCH 01/63] CHANGES: Update --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 250a7dcd4..a63135d32 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Features: - Support Discord Rich Presence - Debugger: Add tracing to file - Map viewer supports bitmapped GBA modes + - OpenGL renderer with high-resolution upscaling support Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs @@ -40,6 +41,7 @@ Other fixes: - Wii: Fix aspect ratio (fixes mgba.io/i/500) - Qt: Fix some Qt display driver race conditions - FFmpeg: Fix audio conversion producing gaps + - Core: Improved lockstep driver reliability (Le Hoang Quyen) Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash From 768aa0ddc2460bae1238af9db5d62c114fb88527 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 19:25:25 -0700 Subject: [PATCH 02/63] GL: Fix FreeBSD build --- src/platform/sdl/gles2-sdl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 675a1139c..1cedc5ad5 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -13,7 +13,7 @@ #include #include -#ifndef __APPLE__ +#ifdef __linux__ #include #endif @@ -37,7 +37,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { size_t size = renderer->width * renderer->height * BYTES_PER_PIXEL; #ifdef _WIN32 renderer->outputBuffer = _aligned_malloc(size, 16); -#elif !defined(__APPLE__) +#elif defined(__linux__) renderer->outputBuffer = memalign(16, size); #else posix_memalign((void**) &renderer->outputBuffer, 16, size); From 320971af93a673400abd7b85427b8ecb763f5dcf Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 19:39:20 -0700 Subject: [PATCH 03/63] Qt: Fix Windows build --- src/platform/qt/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 98a0a9e5a..b0b4e5fff 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -288,6 +288,7 @@ if(QT_STATIC) if(WIN32) list(APPEND QT_LIBRARIES qwindows dwmapi imm32 uxtheme Qt5EventDispatcherSupport Qt5FontDatabaseSupport Qt5ThemeSupport Qt5WindowsUIAutomationSupport) set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32") + set_target_properties(Qt5::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) elseif(APPLE) find_package(Cups) find_package(Qt5PrintSupport) From 921f3f864d8e53c9e33dc149985abaf2eb19c8a1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 19:53:46 -0700 Subject: [PATCH 04/63] Qt: Fall back to 1.4 if 2.x context is bad --- src/platform/qt/DisplayGL.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 0df112e06..1d1e79424 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -34,12 +34,29 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) : Display(parent) , m_gl(nullptr) { + setAttribute(Qt::WA_NativeWindow); + // This can spontaneously re-enter into this->resizeEvent before creation is done, so we // need to make sure it's initialized to nullptr before we assign the new object to it m_gl = new QOpenGLContext; m_gl->setFormat(format); m_gl->create(); - setAttribute(Qt::WA_NativeWindow); + + m_gl->makeCurrent(windowHandle()); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + int majorVersion = m_gl->format().majorVersion(); + QStringList extensions = QString(reinterpret_cast(glGetString(GL_EXTENSIONS))).split(' '); + m_gl->doneCurrent(); + + if (majorVersion == 2 && !extensions.contains("GL_ARB_framebuffer_object")) { + QSurfaceFormat newFormat(format); + newFormat.setVersion(1, 4); + m_gl->setFormat(newFormat); + m_gl->create(); + } + m_painter = new PainterGL(&m_videoProxy, windowHandle(), m_gl); setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions From 6890c070e6e0c868312482a442e2b22f757b5edc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 21:14:51 -0700 Subject: [PATCH 05/63] GBA Video: Fix GL mode 2 distortion --- include/mgba/internal/gba/renderers/gl.h | 1 + src/gba/renderers/gl.c | 49 ++++++++++++++++++------ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 209607f1c..b40103ba8 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -91,6 +91,7 @@ enum { GBA_GL_BG_OFFSET, GBA_GL_BG_INFLAGS, GBA_GL_BG_TRANSFORM, + GBA_GL_BG_RANGE, GBA_GL_OBJ_VRAM = 2, GBA_GL_OBJ_PALETTE, diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 81fc5cdb8..5b2a41eca 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -41,7 +41,7 @@ static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* ren static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y); static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y); -static void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y); +static void _finalizeLayers(struct GBAVideoGLRenderer* renderer); #define TEST_LAYER_ENABLED(X) !renderer->disableBG[X] && glRenderer->bg[X].enabled == 4 @@ -168,6 +168,7 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, { 0 } }; @@ -181,6 +182,7 @@ static const char* const _renderMode2 = "uniform ivec4 inflags;\n" "uniform ivec2[4] offset;\n" "uniform ivec2[4] transform;\n" + "uniform vec2 range;\n" "out vec4 color;\n" "out vec4 flags;\n" "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" @@ -215,8 +217,8 @@ static const char* const _renderMode2 = "}\n" "void main() {\n" - " float y = fract(texCoord.y);\n" - " float lin = 0.5 - y / ceil(y) * 0.25;\n" + " float y = texCoord.y - range.x;\n" + " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n" @@ -842,9 +844,6 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); glRenderer->firstAffine = y; - } else if (y - glRenderer->firstAffine == 1) { - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); } } else { glRenderer->firstAffine = -1; @@ -903,7 +902,6 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { break; } } - _finalizeLayers(glRenderer, y); if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine)); @@ -922,6 +920,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + _finalizeLayers(glRenderer); glRenderer->firstAffine = -1; glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx; glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy; @@ -1021,11 +1020,11 @@ static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, u renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value); } -void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y) { +void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { const GLuint* uniforms = renderer->finalizeShader.uniforms; glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OUTPUT]); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); + glScissor(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glUseProgram(renderer->finalizeShader.program); glBindVertexArray(renderer->finalizeShader.vao); glActiveTexture(GL_TEXTURE0); @@ -1052,7 +1051,7 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y) { glBindTexture(GL_TEXTURE_2D, renderer->bg[3].flags); uint32_t backdrop = M_RGB5_TO_RGB8(renderer->d.palette[0]); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); + glUniform2i(uniforms[GBA_GL_VS_LOC], GBA_VIDEO_VERTICAL_PIXELS, 0); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_FINALIZE_SCALE], renderer->scale); glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 5, 7, 9, 1 }); @@ -1177,24 +1176,50 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, } void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + int reverse; + int forward; + switch (y - renderer->firstAffine) { + case 0: + case 1: + case 2: + case 3: + return; + case 4: + forward = 2; + reverse = 4; + break; + case 5: + forward = 2; + reverse = 3; + break; + case 6: + forward = 2; + reverse = 2; + break; + default: + forward = 1; + reverse = 1; + } + const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3]; const GLuint* uniforms = shader->uniforms; glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); + glScissor(0, (y - reverse) * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * forward); glUseProgram(shader->program); glBindVertexArray(shader->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->vramTex); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); + glUniform2i(uniforms[GBA_GL_VS_LOC], forward, y - reverse); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); + glUniform2f(uniforms[GBA_GL_BG_RANGE], y, 1); glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); From 876c3051c2d056a6fc1e7fb5b37b9718756feb3a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 21:29:01 -0700 Subject: [PATCH 06/63] GBA Video: Move bldy to WINDOW tex --- src/gba/renderers/gl.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 5b2a41eca..00f8465d3 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -314,8 +314,8 @@ static const char* const _finalize = "}\n" "void main() {\n" - " ivec2 windowFlags = ivec2(texelFetch(window, ivec2(texCoord * scale), 0).xy * 32);\n" - " int layerWindow = windowFlags.x | (windowFlags.y << 4);\n" + " vec4 windowFlags = texelFetch(window, ivec2(texCoord * scale), 0);\n" + " int layerWindow = int(windowFlags.x * 32) | int(windowFlags.y * 512);\n" " vec4 topPixel = backdrop;\n" " vec4 bottomPixel = backdrop;\n" " ivec4 topFlags = ivec4(backdropFlags * flagCoeff);\n" @@ -354,9 +354,9 @@ static const char* const _finalize = " topPixel += bottomPixel * bottomFlags.w / 16.;\n" " }\n" " } else if ((topFlags.y & 13) == 9) {\n" - " topPixel += (1. - topPixel) * topFlags.z / 16.;\n" + " topPixel += (1. - topPixel) * windowFlags.z;\n" " } else if ((topFlags.y & 13) == 13) {\n" - " topPixel -= topPixel * topFlags.z / 16.;\n" + " topPixel -= topPixel * windowFlags.z;\n" " }\n" " color = topPixel;\n" "}"; @@ -459,7 +459,7 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]); - _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RG, GL_COLOR_ATTACHMENT0, glRenderer->scale); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OUTPUT]); _initFramebufferTexture(glRenderer->outputTex, GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); @@ -1058,8 +1058,7 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, - (renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy) / 16.f, renderer->bldb / 16.f); + glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, renderer->blda / 16.f, renderer->bldb / 16.f); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -1111,8 +1110,8 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride); glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c)); glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c) << 3, - (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4), - blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); + (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4), + renderer->blda, renderer->bldb); if (GBAObjAttributesAIsTransformed(sprite->a)) { struct GBAOAMMatrix mat; LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); @@ -1168,7 +1167,7 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); + renderer->blda, renderer->bldb); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); @@ -1221,8 +1220,8 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); glUniform2f(uniforms[GBA_GL_BG_RANGE], y, 1); glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, - background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); + background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), + renderer->blda, renderer->bldb); if (renderer->scale > 1) { glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { background->affine[0].sx, background->affine[0].sy, @@ -1256,10 +1255,10 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale) { +static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale, int bldy) { glScissor(start, y, end - start, scale); window = ~window & 0xFF; - glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, 0, 0); + glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, bldy / 16.f, 0); glClear(GL_COLOR_BUFFER_BIT); } @@ -1267,14 +1266,14 @@ void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]); int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { - _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); } else { - _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { - _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start * renderer->scale, renderer->winN[1].h.end * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start * renderer->scale, renderer->winN[1].h.end * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); } if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { - _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start * renderer->scale, renderer->winN[0].h.end * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start * renderer->scale, renderer->winN[0].h.end * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); } } } From ebb6d659455fca127e88d3e3eb0f4b81099a09d3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 21:41:35 -0700 Subject: [PATCH 07/63] GBA Video: Fix GL degenerate windows --- src/gba/renderers/gl.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 00f8465d3..f3a76d0af 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -1256,7 +1256,12 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, } static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale, int bldy) { - glScissor(start, y, end - start, scale); + if (start > end) { + _clearWindow(window, start, GBA_VIDEO_HORIZONTAL_PIXELS, y, scale, bldy); + _clearWindow(window, 0, end, y, scale, bldy); + return; + } + glScissor(start * scale, y, (end - start) * scale, scale); window = ~window & 0xFF; glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, bldy / 16.f, 0); glClear(GL_COLOR_BUFFER_BIT); @@ -1266,14 +1271,14 @@ void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]); int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { - _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS, y * renderer->scale, renderer->scale, renderer->bldy); } else { - _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS, y * renderer->scale, renderer->scale, renderer->bldy); if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { - _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start * renderer->scale, renderer->winN[1].h.end * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start, renderer->winN[1].h.end, y * renderer->scale, renderer->scale, renderer->bldy); } if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { - _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start * renderer->scale, renderer->winN[0].h.end * renderer->scale, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start, renderer->winN[0].h.end, y * renderer->scale, renderer->scale, renderer->bldy); } } } From 43180dca1df957a9e82f08edbf02cd19b00248e8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 00:37:54 -0700 Subject: [PATCH 08/63] GBA Video: Window interpolation --- include/mgba/internal/gba/renderers/gl.h | 2 +- src/gba/renderers/gl.c | 88 +++++++++++++++++------- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index b40103ba8..479a0b66f 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -163,7 +163,7 @@ struct GBAVideoGLRenderer { GBAMosaicControl mosaic; struct GBAVideoGLWindowN { - struct GBAVideoWindowRegion h; + struct GBAVideoWindowRegion h[2]; struct GBAVideoWindowRegion v; GBAWindowControl control; } winN[2]; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index f3a76d0af..805c3f943 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -727,28 +727,28 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, glRenderer->bldy = value; break; case REG_WIN0H: - glRenderer->winN[0].h.end = value; - glRenderer->winN[0].h.start = value >> 8; - if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[0].h.start > glRenderer->winN[0].h.end) { - glRenderer->winN[0].h.start = 0; + glRenderer->winN[0].h[0].end = value; + glRenderer->winN[0].h[0].start = value >> 8; + if (glRenderer->winN[0].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[0].h[0].start > glRenderer->winN[0].h[0].end) { + glRenderer->winN[0].h[0].start = 0; } - if (glRenderer->winN[0].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[0].h.end = GBA_VIDEO_HORIZONTAL_PIXELS; - if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[0].h.start = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[0].h[0].end > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[0].h[0].end = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[0].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[0].h[0].start = GBA_VIDEO_HORIZONTAL_PIXELS; } } break; case REG_WIN1H: - glRenderer->winN[1].h.end = value; - glRenderer->winN[1].h.start = value >> 8; - if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[1].h.start > glRenderer->winN[1].h.end) { - glRenderer->winN[1].h.start = 0; + glRenderer->winN[1].h[0].end = value; + glRenderer->winN[1].h[0].start = value >> 8; + if (glRenderer->winN[1].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[1].h[0].start > glRenderer->winN[1].h[0].end) { + glRenderer->winN[1].h[0].start = 0; } - if (glRenderer->winN[1].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[1].h.end = GBA_VIDEO_HORIZONTAL_PIXELS; - if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[1].h.start = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[1].h[0].end > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[1].h[0].end = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[1].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[1].h[0].start = GBA_VIDEO_HORIZONTAL_PIXELS; } } break; @@ -820,6 +820,9 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glRenderer->vramDirty = 0; if (y == 0) { + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); + glDisable(GL_SCISSOR_TEST); glClearColor(0, 0, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]); @@ -916,6 +919,8 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx; glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy; } + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); } void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { @@ -1255,30 +1260,63 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale, int bldy) { +static void _scissorWindow(int start, int end, int y, int lines, int scale) { if (start > end) { - _clearWindow(window, start, GBA_VIDEO_HORIZONTAL_PIXELS, y, scale, bldy); - _clearWindow(window, 0, end, y, scale, bldy); + _scissorWindow(start, GBA_VIDEO_HORIZONTAL_PIXELS * scale, y, lines, scale); + _scissorWindow(0, end, y, lines, scale); return; } - glScissor(start * scale, y, (end - start) * scale, scale); + glScissor(start, y, end - start, lines); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void _scissorWindowN(struct GBAVideoWindowRegion* region, int y, int scale) { + int sdelta = region[0].start - region[1].start; + int edelta = region[0].end - region[1].end; + int maxDelta = 0; + if (sdelta > maxDelta) { + maxDelta = sdelta; + } else if (-sdelta > maxDelta) { + maxDelta = -sdelta; + } + if (edelta > maxDelta) { + maxDelta = edelta; + } else if (-edelta > maxDelta) { + maxDelta = -edelta; + } + if (!(sdelta | edelta) || maxDelta >= GBA_VIDEO_VERTICAL_PIXELS / 2) { + _scissorWindow(region[0].start * scale, region[0].end * scale, y, scale, scale); + } else { + int i; + for (i = 0; i < scale; ++i) { + int start = region[1].start * scale + sdelta * i; + int end = region[1].end * scale + edelta * i; + _scissorWindow(start, end, y + i, 1, scale); + } + } +} + +static void _clearWindow(GBAWindowControl window, int bldy) { window = ~window & 0xFF; glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, bldy / 16.f, 0); - glClear(GL_COLOR_BUFFER_BIT); } void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]); int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { - _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(dispcnt, renderer->bldy); + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); } else { - _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winout & dispcnt, renderer->bldy); + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { - _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start, renderer->winN[1].h.end, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winN[1].control & dispcnt, renderer->bldy); + _scissorWindowN(renderer->winN[1].h, y * renderer->scale, renderer->scale); } if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { - _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start, renderer->winN[0].h.end, y * renderer->scale, renderer->scale, renderer->bldy); + _clearWindow(renderer->winN[0].control & dispcnt, renderer->bldy); + _scissorWindowN(renderer->winN[0].h, y * renderer->scale, renderer->scale); } } } From 71b6066d4f6aecc85dc1dafc92cecaf6154f8f4a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 01:09:29 -0700 Subject: [PATCH 09/63] GBA Video: Fix some GL handle leaks --- src/gba/renderers/gl.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 805c3f943..96a22501b 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -390,7 +390,7 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) { renderer->scale = 1; } -void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) { +static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) { GLuint program = glCreateProgram(); shader->program = program; @@ -426,6 +426,11 @@ void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShad } } +static void _deleteShader(struct GBAVideoGLShader* shader) { + glDeleteProgram(shader->program); + glDeleteVertexArrays(1, &shader->vao); +} + static void _initFramebufferTexture(GLuint tex, GLenum format, GLenum attachment, int scale) { glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -564,6 +569,22 @@ void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer) { glDeleteTextures(GBA_GL_TEX_MAX, glRenderer->layers); glDeleteTextures(1, &glRenderer->paletteTex); glDeleteTextures(1, &glRenderer->vramTex); + + _deleteShader(&glRenderer->bgShader[0]); + _deleteShader(&glRenderer->bgShader[1]); + _deleteShader(&glRenderer->bgShader[2]); + _deleteShader(&glRenderer->bgShader[3]); + _deleteShader(&glRenderer->objShader[0]); + _deleteShader(&glRenderer->objShader[1]); + _deleteShader(&glRenderer->finalizeShader); + + int i; + for (i = 0; i < 4; ++i) { + struct GBAVideoGLBackground* bg = &glRenderer->bg[i]; + glDeleteFramebuffers(1, &bg->fbo); + glDeleteTextures(1, &bg->tex); + glDeleteTextures(1, &bg->flags); + } } void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) { From 91cd47ee0e6c710d6f59ac3a7a5639fcd48f23e2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 10:38:23 -0700 Subject: [PATCH 10/63] GBA Video: Fix OBJWIN in GL renderer --- src/gba/renderers/gl.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 96a22501b..76365a965 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -250,10 +250,10 @@ static const char* const _renderObj = "uniform ivec4 inflags;\n" "uniform mat2x2 transform;\n" "uniform ivec4 dims;\n" - "uniform vec3 objwin;\n" + "uniform vec4 objwin;\n" "out vec4 color;\n" "out vec4 flags;\n" - "out vec2 window;\n" + "out vec3 window;\n" "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" @@ -269,7 +269,7 @@ static const char* const _renderObj = " }\n" " color = pix;\n" " flags = inflags / flagCoeff;\n" - " window = objwin.yz;\n" + " window = objwin.yzw;\n" "}"; static const struct GBAVideoGLUniform _uniformsFinalize[] = { @@ -462,6 +462,7 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_COLOR], GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGBA, GL_COLOR_ATTACHMENT2, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); @@ -1152,10 +1153,10 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) { int window = ~renderer->objwin & 0xFF; - glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 1, (window & 0xF) / 32.f, (window >> 4) / 32.f); + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, (window & 0xF) / 32.f, (window >> 4) / 32.f, renderer->bldy / 16.f); glDrawBuffers(3, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }); } else { - glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0); + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } glEnableVertexAttribArray(0); From 3abee66ae6fd1b572448d2ad241f2f531e1fe4fc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 13:53:13 -0700 Subject: [PATCH 11/63] GBA Video: Mode 2 tweaks --- src/gba/renderers/gl.c | 51 ++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 76365a965..ee0dcff77 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -221,7 +221,7 @@ static const char* const _renderMode2 = " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n" + " color = fetchTile(ivec2(mixedTransform * (texCoord.x - 1) + mixedOffset));\n" " flags = inflags / flagCoeff;\n" "}"; @@ -1202,29 +1202,32 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, } void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { - int reverse; - int forward; - switch (y - renderer->firstAffine) { - case 0: - case 1: - case 2: - case 3: - return; - case 4: - forward = 2; - reverse = 4; - break; - case 5: - forward = 2; - reverse = 3; - break; - case 6: - forward = 2; - reverse = 2; - break; - default: - forward = 1; - reverse = 1; + int reverse = 0; + int forward = 1; + if (renderer->scale > 1) { + switch (y - renderer->firstAffine) { + case 0: + case 1: + case 2: + case 3: + return; + case 4: + forward = 2; + reverse = 4; + break; + case 5: + forward = 2; + reverse = 3; + break; + case 6: + forward = 2; + reverse = 2; + break; + case 7: + forward = 2; + reverse = 1; + break; + } } const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3]; From c5ae273a3a786a00dbe470fac01885819e86e958 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 14:00:34 -0700 Subject: [PATCH 12/63] GBA Video: Move all window bits to x --- src/gba/renderers/gl.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index ee0dcff77..24ec58d70 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -13,6 +13,8 @@ #include #include +#define FLAG_CONST "const vec4 flagCoeff = vec4(64., 32., 16., 16.);\n" + static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer); static void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer); @@ -116,7 +118,7 @@ static const char* const _renderMode0 = "uniform ivec4 inflags;\n" "out vec4 color;\n" "out vec4 flags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" @@ -185,7 +187,7 @@ static const char* const _renderMode2 = "uniform vec2 range;\n" "out vec4 color;\n" "out vec4 flags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "precision highp float;\n" "precision highp int;\n" @@ -254,7 +256,7 @@ static const char* const _renderObj = "out vec4 color;\n" "out vec4 flags;\n" "out vec3 window;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" @@ -292,7 +294,7 @@ static const char* const _finalize = "uniform sampler2D window;\n" "uniform vec4 backdrop;\n" "uniform vec4 backdropFlags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "out vec4 color;\n" "void composite(vec4 pixel, ivec4 flags, inout vec4 topPixel, inout ivec4 topFlags, inout vec4 bottomPixel, inout ivec4 bottomFlags) {\n" @@ -315,7 +317,7 @@ static const char* const _finalize = "void main() {\n" " vec4 windowFlags = texelFetch(window, ivec2(texCoord * scale), 0);\n" - " int layerWindow = int(windowFlags.x * 32) | int(windowFlags.y * 512);\n" + " int layerWindow = int(windowFlags.x * 128);\n" " vec4 topPixel = backdrop;\n" " vec4 bottomPixel = backdrop;\n" " ivec4 topFlags = ivec4(backdropFlags * flagCoeff);\n" @@ -1085,7 +1087,7 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, renderer->blda / 16.f, renderer->bldb / 16.f); + glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 64.f, renderer->blda / 16.f, renderer->bldb / 16.f); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -1152,8 +1154,8 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB } glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) { - int window = ~renderer->objwin & 0xFF; - glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, (window & 0xF) / 32.f, (window >> 4) / 32.f, renderer->bldy / 16.f); + int window = ~renderer->objwin & 0x3F; + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, window / 128.f, 0, renderer->bldy / 16.f); glDrawBuffers(3, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }); } else { glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); @@ -1322,8 +1324,8 @@ static void _scissorWindowN(struct GBAVideoWindowRegion* region, int y, int scal } static void _clearWindow(GBAWindowControl window, int bldy) { - window = ~window & 0xFF; - glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, bldy / 16.f, 0); + window = ~window & 0x3F; + glClearColor(window / 128.f, 0, bldy / 16.f, 0); } void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { From 28bd97310da51fc5c1cfe57932140ec465f941a8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 14:20:08 -0700 Subject: [PATCH 13/63] GBA Video: Move bldb to window w --- src/gba/renderers/gl.c | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 24ec58d70..c9a968334 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -13,7 +13,7 @@ #include #include -#define FLAG_CONST "const vec4 flagCoeff = vec4(64., 32., 16., 16.);\n" +#define FLAG_CONST "const vec4 flagCoeff = vec4(64., 32., 16., 1.);\n" static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer); @@ -324,36 +324,36 @@ static const char* const _finalize = " ivec4 bottomFlags = ivec4(backdropFlags * flagCoeff);\n" " if ((layerWindow & 1) == 0) {\n" " vec4 pix = texelFetch(layers[0], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 2) == 0) {\n" " vec4 pix = texelFetch(layers[1], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[1], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[1], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 4) == 0) {\n" " vec4 pix = texelFetch(layers[2], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[2], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[2], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 8) == 0) {\n" " vec4 pix = texelFetch(layers[3], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[3], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[3], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" - " if ((layerWindow & 32) != 0) {\n" - " topFlags.y &= ~1;\n" - " }\n" " if ((layerWindow & 16) == 0) {\n" " vec4 pix = texelFetch(layers[4], ivec2(texCoord * scale), 0);\n" " ivec4 inflags = ivec4(texelFetch(flags[4], ivec2(texCoord * scale), 0) * flagCoeff);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" - " if ((topFlags.y & 13) == 5) {\n" + " if ((layerWindow & 32) != 0) {\n" + " topFlags.y &= ~1;\n" + " }\n" + " if ((topFlags.y & 13) == 5 || topFlags.w > 0) {\n" " if ((bottomFlags.y & 2) == 2) {\n" " topPixel *= topFlags.z / 16.;\n" - " topPixel += bottomPixel * bottomFlags.w / 16.;\n" + " topPixel += bottomPixel * windowFlags.y;\n" " }\n" " } else if ((topFlags.y & 13) == 9) {\n" " topPixel += (1. - topPixel) * windowFlags.z;\n" @@ -507,7 +507,7 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { glGenTextures(1, &bg->flags); glBindFramebuffer(GL_FRAMEBUFFER, bg->fbo); _initFramebufferTexture(bg->tex, GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale); - _initFramebufferTexture(bg->flags, GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); + _initFramebufferTexture(bg->flags, GL_RGB, GL_COLOR_ATTACHMENT1, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -1087,7 +1087,7 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 64.f, renderer->blda / 16.f, renderer->bldb / 16.f); + glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 64.f, renderer->blda / 16.f, 0); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -1140,7 +1140,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c)); glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c) << 3, (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4), - renderer->blda, renderer->bldb); + renderer->blda, GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); if (GBAObjAttributesAIsTransformed(sprite->a)) { struct GBAOAMMatrix mat; LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); @@ -1155,7 +1155,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) { int window = ~renderer->objwin & 0x3F; - glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, window / 128.f, 0, renderer->bldy / 16.f); + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, window / 128.f, renderer->bldb / 16.f, renderer->bldy / 16.f); glDrawBuffers(3, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }); } else { glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); @@ -1196,7 +1196,7 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blda, renderer->bldb); + renderer->blda, 0); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); @@ -1253,7 +1253,7 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glUniform2f(uniforms[GBA_GL_BG_RANGE], y, 1); glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blda, renderer->bldb); + renderer->blda, 0); if (renderer->scale > 1) { glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { background->affine[0].sx, background->affine[0].sy, @@ -1323,26 +1323,26 @@ static void _scissorWindowN(struct GBAVideoWindowRegion* region, int y, int scal } } -static void _clearWindow(GBAWindowControl window, int bldy) { +static void _clearWindow(GBAWindowControl window, int bldb, int bldy) { window = ~window & 0x3F; - glClearColor(window / 128.f, 0, bldy / 16.f, 0); + glClearColor(window / 128.f, bldb / 16.f, bldy / 16.f, 0); } void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]); int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { - _clearWindow(dispcnt, renderer->bldy); + _clearWindow(dispcnt, renderer->bldb, renderer->bldy); _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); } else { - _clearWindow(renderer->winout & dispcnt, renderer->bldy); + _clearWindow(renderer->winout & dispcnt, renderer->bldb, renderer->bldy); _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { - _clearWindow(renderer->winN[1].control & dispcnt, renderer->bldy); + _clearWindow(renderer->winN[1].control & dispcnt, renderer->bldb, renderer->bldy); _scissorWindowN(renderer->winN[1].h, y * renderer->scale, renderer->scale); } if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { - _clearWindow(renderer->winN[0].control & dispcnt, renderer->bldy); + _clearWindow(renderer->winN[0].control & dispcnt, renderer->bldb, renderer->bldy); _scissorWindowN(renderer->winN[0].h, y * renderer->scale, renderer->scale); } } From 20ea552d17f66519231b4d81c815a522b18c71a3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 15:40:32 -0700 Subject: [PATCH 14/63] GBA Video: GL mode 4, cleanup --- src/gba/renderers/gl.c | 166 +++++++++++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 48 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index c9a968334..4b9dcd2aa 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -174,6 +174,15 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { 0 } }; +static const char* const _interpolate = + "vec2 interpolate(ivec2 arr[4], float x) {\n" + " float x1m = 1. - x;\n" + " return x1m * x1m * x1m * arr[0] +" + " 3 * x1m * x1m * x * arr[1] +" + " 3 * x1m * x * x * arr[2] +" + " x * x * x * arr[3];\n" + "}\n"; + static const char* const _renderMode2 = "in vec2 texCoord;\n" "uniform sampler2D vram;\n" @@ -192,6 +201,7 @@ static const char* const _renderMode2 = "precision highp int;\n" "vec4 fetchTile(ivec2 coord);\n" + "vec2 interpolate(ivec2 arr[4], float x);\n" "vec4 renderTile(ivec2 coord) {\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" @@ -210,20 +220,64 @@ static const char* const _renderMode2 = " return color;\n" "}\n" - "vec2 interpolate(ivec2 arr[4], float x) {\n" - " float x1m = 1. - x;\n" - " return x1m * x1m * x1m * arr[0] +" - " 3 * x1m * x1m * x * arr[1] +" - " 3 * x1m * x * x * arr[2] +" - " x * x * x * arr[3];\n" - "}\n" + "void main() {\n" + " float y = texCoord.y - range.x;\n" + " float lin = 0.5 - y / range.y * 0.25;\n" + " vec2 mixedTransform = interpolate(transform, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n" + " flags = inflags / flagCoeff;\n" + "}"; + +static const struct GBAVideoGLUniform _uniformsMode4[] = { + { "loc", GBA_GL_VS_LOC, }, + { "maxPos", GBA_GL_VS_MAXPOS, }, + { "vram", GBA_GL_BG_VRAM, }, + { "palette", GBA_GL_BG_PALETTE, }, + { "charBase", GBA_GL_BG_CHARBASE, }, + { "size", GBA_GL_BG_SIZE, }, + { "inflags", GBA_GL_BG_INFLAGS, }, + { "offset", GBA_GL_BG_OFFSET, }, + { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, + { 0 } +}; + +static const char* const _renderMode4 = + "in vec2 texCoord;\n" + "uniform sampler2D vram;\n" + "uniform sampler2D palette;\n" + "uniform int charBase;\n" + "uniform ivec2 size;\n" + "uniform ivec4 inflags;\n" + "uniform ivec2[4] offset;\n" + "uniform ivec2[4] transform;\n" + "uniform vec2 range;\n" + "out vec4 color;\n" + "out vec4 flags;\n" + FLAG_CONST + "precision highp float;\n" + "precision highp int;\n" + + "vec2 interpolate(ivec2 arr[4], float x);\n" "void main() {\n" " float y = texCoord.y - range.x;\n" " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " color = fetchTile(ivec2(mixedTransform * (texCoord.x - 1) + mixedOffset));\n" + " ivec2 coord = ivec2(mixedTransform * texCoord.x + mixedOffset);\n" + " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" + " discard;\n" + " }\n" + " if (coord.y < 0 || coord.y >= (size.y << 8)) {\n" + " discard;\n" + " }\n" + " int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n" + " vec4 twoEntries = texelFetch(vram, ivec2((address >> 1) & 255, address >> 9), 0);\n" + " ivec2 entry = ivec2(twoEntries[3 - 2 * (address & 1)] * 15.9, twoEntries[2 - 2 * (address & 1)] * 15.9);\n" + " color = texelFetch(palette, entry, 0);\n" + " color.a = 1;\n" " flags = inflags / flagCoeff;\n" "}"; @@ -533,12 +587,17 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { _compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, log); shaderBuffer[1] = _renderMode2; + shaderBuffer[2] = _interpolate; - shaderBuffer[2] = _fetchTileOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 3, vs, _uniformsMode2, log); + shaderBuffer[3] = _fetchTileOverflow; + _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, log); - shaderBuffer[2] = _fetchTileNoOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 3, vs, _uniformsMode2, log); + shaderBuffer[3] = _fetchTileNoOverflow; + _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, log); + + shaderBuffer[1] = _renderMode4; + shaderBuffer[2] = _interpolate; + _compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, log); shaderBuffer[1] = _renderObj; @@ -912,7 +971,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { //GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y); break; case 4: - //GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y); break; case 5: //GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y); @@ -1166,6 +1225,22 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } +void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, renderer->vramTex); + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); + glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); + glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); + glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); + glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, + background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), + renderer->blda, 0); + glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); +} + void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { int inY = y + background->y; int yBase = inY & 0xFF; @@ -1177,33 +1252,21 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, const struct GBAVideoGLShader* shader = &renderer->bgShader[background->multipalette ? 1 : 0]; const GLuint* uniforms = shader->uniforms; - glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); - glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, renderer->vramTex); - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); + _prepareBackground(renderer, background, uniforms, y); glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); - glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); - glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); - glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, - background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blda, 0); - glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { +void _prepareTransform(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { int reverse = 0; int forward = 1; if (renderer->scale > 1) { @@ -1232,28 +1295,9 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, } } - const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3]; - const GLuint* uniforms = shader->uniforms; - glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); - glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glScissor(0, (y - reverse) * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * forward); - glUseProgram(shader->program); - glBindVertexArray(shader->vao); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, renderer->vramTex); - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); glUniform2i(uniforms[GBA_GL_VS_LOC], forward, y - reverse); - glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); - glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); - glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); - glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); - glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); - glUniform2f(uniforms[GBA_GL_BG_RANGE], y, 1); - glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, - background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blda, 0); + glUniform2f(uniforms[GBA_GL_BG_RANGE], y - 1, 1); if (renderer->scale > 1) { glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { background->affine[0].sx, background->affine[0].sy, @@ -1281,7 +1325,33 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, background->affine[0].dx, background->affine[0].dy, }); } - glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); + _prepareBackground(renderer, background, uniforms, y); +} + +void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3]; + const GLuint* uniforms = shader->uniforms; + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); + glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); + glEnableVertexAttribArray(0); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + +void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[4]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + _prepareTransform(renderer, background, uniforms, y); + glBindVertexArray(shader->vao); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0xA000 : 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); From 40d21361154ec96fc4c16637d9dafe78d2726528 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 15:46:25 -0700 Subject: [PATCH 15/63] GBA Video: Fix layering regression --- src/gba/renderers/gl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 4b9dcd2aa..f7dc36ddf 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -1146,7 +1146,7 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 64.f, renderer->blda / 16.f, 0); + glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, renderer->blda / 16.f, 0); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); From 3a3d01aedc384e24b2de40c403669cf337587039 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 16:10:12 -0700 Subject: [PATCH 16/63] GBA Video: Fix forced transparency OBJ blending in GL --- src/gba/renderers/gl.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index f7dc36ddf..dc20f3c0f 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -404,11 +404,9 @@ static const char* const _finalize = " if ((layerWindow & 32) != 0) {\n" " topFlags.y &= ~1;\n" " }\n" - " if ((topFlags.y & 13) == 5 || topFlags.w > 0) {\n" - " if ((bottomFlags.y & 2) == 2) {\n" - " topPixel *= topFlags.z / 16.;\n" - " topPixel += bottomPixel * windowFlags.y;\n" - " }\n" + " if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n" + " topPixel *= topFlags.z / 16.;\n" + " topPixel += bottomPixel * windowFlags.y;\n" " } else if ((topFlags.y & 13) == 9) {\n" " topPixel += (1. - topPixel) * windowFlags.z;\n" " } else if ((topFlags.y & 13) == 13) {\n" @@ -1177,8 +1175,6 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB totalHeight <<= 1; } - enum GBAVideoBlendEffect blendEffect = GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT ? BLEND_ALPHA : renderer->blendEffect; - const struct GBAVideoGLShader* shader = &renderer->objShader[GBAObjAttributesAGet256Color(sprite->a)]; const GLuint* uniforms = shader->uniforms; glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OBJ]); @@ -1198,7 +1194,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride); glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c)); glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c) << 3, - (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4), + (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (renderer->blendEffect * 4), renderer->blda, GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); if (GBAObjAttributesAIsTransformed(sprite->a)) { struct GBAOAMMatrix mat; From 7cc06ea12db77b7c8c70c6c2a9cc841624e98774 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 16:57:24 -0700 Subject: [PATCH 17/63] Qt: Increase maximum magnifications and scaling --- CHANGES | 1 + src/platform/qt/MapView.ui | 2 +- src/platform/qt/ObjView.ui | 2 +- src/platform/qt/SettingsView.ui | 2 +- src/platform/qt/TileView.ui | 2 +- src/platform/qt/Window.cpp | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index a63135d32..dbf38268f 100644 --- a/CHANGES +++ b/CHANGES @@ -61,6 +61,7 @@ Misc: - Qt: Open a message box for Qt frontend errors - GBA Video: Clean up dead code in sprite rendering loop - FFmpeg: Support audio-only recording + - Qt: Increase maximum magnifications and scaling 0.7.1: (2019-02-24) Bugfixes: diff --git a/src/platform/qt/MapView.ui b/src/platform/qt/MapView.ui index 15a1f5f74..d07a832b5 100644 --- a/src/platform/qt/MapView.ui +++ b/src/platform/qt/MapView.ui @@ -104,7 +104,7 @@ 1 - 4 + 8 diff --git a/src/platform/qt/ObjView.ui b/src/platform/qt/ObjView.ui index 7f33eaeb0..df0a003a9 100644 --- a/src/platform/qt/ObjView.ui +++ b/src/platform/qt/ObjView.ui @@ -59,7 +59,7 @@ 1 - 6 + 8 diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 15d7893f9..23172e691 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -894,7 +894,7 @@ 1 - 13 + 16 diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui index 02c4c533f..4f027a493 100644 --- a/src/platform/qt/TileView.ui +++ b/src/platform/qt/TileView.ui @@ -126,7 +126,7 @@ 1 - 4 + 8 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 15a13d3b9..d1ec28a99 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1284,7 +1284,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addMenu(tr("Audio/&Video"), "av"); m_actions.addMenu(tr("Frame size"), "frame", "av"); - for (int i = 1; i <= 6; ++i) { + for (int i = 1; i <= 8; ++i) { Action* setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() { Action* setSize = m_frameSizes[i]; showNormal(); From 6cf255daf45ac5f9f778d1c4a56cf07a70f48316 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 17:06:53 -0700 Subject: [PATCH 18/63] Qt: Allow small windows --- src/platform/opengl/gles2.c | 2 +- src/platform/qt/Window.cpp | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 35e7ac9e0..c9994f10a 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -246,7 +246,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, oldTex); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index d1ec28a99..4498f8c5d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -719,7 +719,6 @@ void Window::gameStarted() { resizeFrame(size * m_savedScale); } attachWidget(m_display.get()); - m_display->setMinimumSize(size); setFocus(); #ifndef Q_OS_MAC @@ -890,7 +889,6 @@ void Window::reloadDisplayDriver() { #endif if (m_controller) { - m_display->setMinimumSize(m_controller->screenDimensions()); connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); @@ -902,13 +900,12 @@ void Window::reloadDisplayDriver() { attachWidget(m_display.get()); m_display->startDrawing(m_controller); - } else { -#ifdef M_CORE_GB - m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS); -#elif defined(M_CORE_GBA) - m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); -#endif } +#ifdef M_CORE_GB + m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS); +#elif defined(M_CORE_GBA) + m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); +#endif } void Window::reloadAudioDriver() { From 7e476dfb763a832cb5d558602e9f3cbd999cfb3d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 17:26:56 -0700 Subject: [PATCH 19/63] GBA Video: GL modes 3 and 5 --- src/gba/renderers/gl.c | 95 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index dc20f3c0f..eef9a8fdf 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -229,6 +229,55 @@ static const char* const _renderMode2 = " flags = inflags / flagCoeff;\n" "}"; +static const struct GBAVideoGLUniform _uniformsMode35[] = { + { "loc", GBA_GL_VS_LOC, }, + { "maxPos", GBA_GL_VS_MAXPOS, }, + { "vram", GBA_GL_BG_VRAM, }, + { "charBase", GBA_GL_BG_CHARBASE, }, + { "size", GBA_GL_BG_SIZE, }, + { "inflags", GBA_GL_BG_INFLAGS, }, + { "offset", GBA_GL_BG_OFFSET, }, + { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, + { 0 } +}; + +static const char* const _renderMode35 = + "in vec2 texCoord;\n" + "uniform sampler2D vram;\n" + "uniform int charBase;\n" + "uniform ivec2 size;\n" + "uniform ivec4 inflags;\n" + "uniform ivec2[4] offset;\n" + "uniform ivec2[4] transform;\n" + "uniform vec2 range;\n" + "out vec4 color;\n" + "out vec4 flags;\n" + FLAG_CONST + "precision highp float;\n" + "precision highp int;\n" + + "vec2 interpolate(ivec2 arr[4], float x);\n" + + "void main() {\n" + " float y = texCoord.y - range.x;\n" + " float lin = 0.5 - y / range.y * 0.25;\n" + " vec2 mixedTransform = interpolate(transform, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " ivec2 coord = ivec2(mixedTransform * texCoord.x + mixedOffset);\n" + " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" + " discard;\n" + " }\n" + " if (coord.y < 0 || coord.y >= (size.y << 8)) {\n" + " discard;\n" + " }\n" + " int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n" + " ivec4 entry = ivec4(texelFetch(vram, ivec2(address & 255, address >> 8), 0) * 15.9);\n" + " int sixteen = (entry.x << 12) | (entry.y << 8) | (entry.z << 4) | entry.w;\n" + " color = vec4((sixteen & 0x1F) / 31., ((sixteen >> 5) & 0x1F) / 31., ((sixteen >> 10) & 0x1F) / 31., 1.);\n" + " flags = inflags / flagCoeff;\n" + "}"; + static const struct GBAVideoGLUniform _uniformsMode4[] = { { "loc", GBA_GL_VS_LOC, }, { "maxPos", GBA_GL_VS_MAXPOS, }, @@ -597,6 +646,10 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { shaderBuffer[2] = _interpolate; _compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, log); + shaderBuffer[1] = _renderMode35; + shaderBuffer[2] = _interpolate; + _compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, log); + shaderBuffer[1] = _renderObj; shaderBuffer[2] = _renderTile16; @@ -966,13 +1019,13 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { GBAVideoGLRendererDrawBackgroundMode2(glRenderer, &glRenderer->bg[2], y); break; case 3: - //GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y); break; case 4: GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y); break; case 5: - //GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y); break; } } @@ -1221,7 +1274,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { +void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms) { glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glActiveTexture(GL_TEXTURE0); @@ -1251,7 +1304,7 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); - _prepareBackground(renderer, background, uniforms, y); + _prepareBackground(renderer, background, uniforms); glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); @@ -1321,7 +1374,7 @@ void _prepareTransform(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBac background->affine[0].dx, background->affine[0].dy, }); } - _prepareBackground(renderer, background, uniforms, y); + _prepareBackground(renderer, background, uniforms); } void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { @@ -1338,14 +1391,29 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } +void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[5]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); + glEnableVertexAttribArray(0); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { const struct GBAVideoGLShader* shader = &renderer->bgShader[4]; const GLuint* uniforms = shader->uniforms; glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glUseProgram(shader->program); - _prepareTransform(renderer, background, uniforms, y); glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0xA000 : 0); glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glEnableVertexAttribArray(0); @@ -1353,6 +1421,21 @@ void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } +void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[5]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0x5000 : 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], 160, 128); + glEnableVertexAttribArray(0); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + static void _scissorWindow(int start, int end, int y, int lines, int scale) { if (start > end) { _scissorWindow(start, GBA_VIDEO_HORIZONTAL_PIXELS * scale, y, lines, scale); From 4e2052f9341e05a47a20e4060d5dd56d741e0088 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 18:42:45 -0700 Subject: [PATCH 20/63] GBA Audio: Experimental HLE audio --- CMakeLists.txt | 1 + include/mgba/core/cpu.h | 4 + include/mgba/internal/gba/audio.h | 189 +++++++++++ include/mgba/internal/gba/extra/audio-mixer.h | 19 ++ src/gba/audio.c | 50 ++- src/gba/core.c | 18 ++ src/gba/extra/audio-mixer.c | 304 ++++++++++++++++++ src/gba/gba.c | 4 + 8 files changed, 575 insertions(+), 14 deletions(-) create mode 100644 include/mgba/internal/gba/extra/audio-mixer.h create mode 100644 src/gba/extra/audio-mixer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d25191081..ed522e70b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ file(GLOB GBA_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/test/*.c) file(GLOB GB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/*.c) file(GLOB GB_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/test/*.c) file(GLOB GBA_CHEATS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/cheats/*.c) +file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/audio-mixer.c) file(GLOB GBA_RR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB CORE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c) file(GLOB CORE_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/test/*.c) diff --git a/include/mgba/core/cpu.h b/include/mgba/core/cpu.h index fe6caedd3..71f3398c2 100644 --- a/include/mgba/core/cpu.h +++ b/include/mgba/core/cpu.h @@ -13,6 +13,10 @@ CXX_GUARD_START enum mCPUComponentType { CPU_COMPONENT_DEBUGGER, CPU_COMPONENT_CHEAT_DEVICE, + CPU_COMPONENT_MISC_1, + CPU_COMPONENT_MISC_2, + CPU_COMPONENT_MISC_3, + CPU_COMPONENT_MISC_4, CPU_COMPONENT_MAX }; diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index 10f5a7e65..f08f845b6 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -10,10 +10,14 @@ CXX_GUARD_START +#include #include #include #include +#define MP2K_MAGIC 0x68736D53 +#define MP2K_MAX_SOUND_CHANNELS 12 + mLOG_DECLARE_CATEGORY(GBA_AUDIO); struct GBADMA; @@ -44,6 +48,7 @@ DECL_BITFIELD(GBARegisterSOUNDBIAS, uint16_t); DECL_BITS(GBARegisterSOUNDBIAS, Bias, 0, 10); DECL_BITS(GBARegisterSOUNDBIAS, Resolution, 14, 2); +struct GBAAudioMixer; struct GBAAudio { struct GBA* p; @@ -71,6 +76,8 @@ struct GBAAudio { GBARegisterSOUNDBIAS soundbias; + struct GBAAudioMixer* mixer; + bool externalMixing; int32_t sampleInterval; bool forceDisableChA; @@ -85,6 +92,188 @@ struct GBAStereoSample { int16_t right; }; +struct GBAMP2kADSR { + uint8_t attack; + uint8_t decay; + uint8_t sustain; + uint8_t release; +}; + +struct GBAMP2kSoundChannel { + uint8_t status; + uint8_t type; + uint8_t rightVolume; + uint8_t leftVolume; + struct GBAMP2kADSR adsr; + uint8_t ky; + uint8_t envelopeV; + uint8_t envelopeRight; + uint8_t envelopeLeft; + uint8_t echoVolume; + uint8_t echoLength; + uint8_t d1; + uint8_t d2; + uint8_t gt; + uint8_t midiKey; + uint8_t ve; + uint8_t pr; + uint8_t rp; + uint8_t d3[3]; + uint32_t ct; + uint32_t fw; + uint32_t freq; + uint32_t waveData; + uint32_t cp; + uint32_t track; + uint32_t pp; + uint32_t np; + uint32_t d4; + uint16_t xpi; + uint16_t xpc; +}; + +struct GBAMP2kContext { + uint32_t magic; + uint8_t pcmDmaCounter; + uint8_t reverb; + uint8_t maxChans; + uint8_t masterVolume; + uint8_t freq; + uint8_t mode; + uint8_t c15; + uint8_t pcmDmaPeriod; + uint8_t maxLines; + uint8_t gap[3]; + int32_t pcmSamplesPerVBlank; + int32_t pcmFreq; + int32_t divFreq; + uint32_t cgbChans; + uint32_t func; + uint32_t intp; + uint32_t cgbSound; + uint32_t cgbOscOff; + uint32_t midiKeyToCgbFreq; + uint32_t mPlayJumpTable; + uint32_t plynote; + uint32_t extVolPit; + uint8_t gap2[16]; + struct GBAMP2kSoundChannel chans[MP2K_MAX_SOUND_CHANNELS]; +}; + +struct GBAMP2kMusicPlayerInfo { + uint32_t songHeader; + uint32_t status; + uint8_t trackCount; + uint8_t priority; + uint8_t cmd; + uint8_t unk_B; + uint32_t clock; + uint8_t gap[8]; + uint32_t memAccArea; + uint16_t tempoD; + uint16_t tempoU; + uint16_t tempoI; + uint16_t tempoC; + uint16_t fadeOI; + uint16_t fadeOC; + uint16_t fadeOV; + uint32_t tracks; + uint32_t tone; + uint32_t magic; + uint32_t func; + uint32_t intp; +}; + +struct GBAMP2kInstrument { + uint8_t type; + uint8_t key; + uint8_t length; + union { + uint8_t pan; + uint8_t sweep; + } ps; + union { + uint32_t waveData; + uint32_t subTable; + } data; + union { + struct GBAMP2kADSR adsr; + uint32_t map; + } extInfo; +}; + +struct GBAMP2kMusicPlayerTrack { + uint8_t flags; + uint8_t wait; + uint8_t patternLevel; + uint8_t repN; + uint8_t gateTime; + uint8_t key; + uint8_t velocity; + uint8_t runningStatus; + uint8_t keyM; + uint8_t pitM; + int8_t keyShift; + int8_t keyShiftX; + int8_t tune; + uint8_t pitX; + int8_t bend; + uint8_t bendRange; + uint8_t volMR; + uint8_t volML; + uint8_t vol; + uint8_t volX; + int8_t pan; + int8_t panX; + int8_t modM; + uint8_t mod; + uint8_t modT; + uint8_t lfoSpeed; + uint8_t lfoSpeedC; + uint8_t lfoDelay; + uint8_t lfoDelayC; + uint8_t priority; + uint8_t echoVolume; + uint8_t echoLength; + uint32_t chan; + struct GBAMP2kInstrument instrument; + uint8_t gap[10]; + uint16_t unk_3A; + uint32_t unk_3C; + uint32_t cmdPtr; + uint32_t patternStack[3]; +}; + +struct GBAMP2kTrack { + struct GBAMP2kMusicPlayerTrack track; + struct GBAMP2kSoundChannel* channel; + uint8_t lastCommand; + struct CircleBuffer buffer; + uint32_t samplePlaying; + float currentOffset; + bool waiting; +}; + +struct GBAAudioMixer { + struct mCPUComponent d; + struct GBAAudio* p; + + uint32_t contextAddress; + + bool (*engage)(struct GBAAudioMixer* mixer, uint32_t address); + void (*vblank)(struct GBAAudioMixer* mixer); + void (*step)(struct GBAAudioMixer* mixer); + + struct GBAMP2kContext context; + struct GBAMP2kMusicPlayerInfo player; + struct GBAMP2kTrack activeTracks[MP2K_MAX_SOUND_CHANNELS]; + + double tempo; + double frame; + + struct GBAStereoSample last; +}; + void GBAAudioInit(struct GBAAudio* audio, size_t samples); void GBAAudioReset(struct GBAAudio* audio); void GBAAudioDeinit(struct GBAAudio* audio); diff --git a/include/mgba/internal/gba/extra/audio-mixer.h b/include/mgba/internal/gba/extra/audio-mixer.h new file mode 100644 index 000000000..369a4aaa3 --- /dev/null +++ b/include/mgba/internal/gba/extra/audio-mixer.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * 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/. */ +#ifndef GBA_AUDIO_MIXER_H +#define GBA_AUDIO_MIXER_H + +#include + +CXX_GUARD_START + +#include + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer); + +CXX_GUARD_END + +#endif diff --git a/src/gba/audio.c b/src/gba/audio.c index c0ed7cf4c..b337043a3 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -14,6 +14,8 @@ #include #include +#define MP2K_LOCK_MAX 8 + #ifdef _3DS #define blip_add_delta blip_add_delta_fast #endif @@ -24,7 +26,7 @@ const unsigned GBA_AUDIO_SAMPLES = 2048; const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); const int GBA_AUDIO_VOLUME_MAX = 0x100; -static const int CLOCKS_PER_FRAME = 0x400; +static const int CLOCKS_PER_FRAME = 0x800; static int _applyBias(struct GBAAudio* audio, int sample); static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); @@ -49,6 +51,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); + audio->externalMixing = false; audio->forceDisableChA = false; audio->forceDisableChB = false; audio->masterVolume = GBA_AUDIO_VOLUME_MAX; @@ -111,6 +114,20 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } + uint32_t source = info->source; + uint32_t magic[2] = { + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x350, NULL), + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x980, NULL) + }; + if (audio->mixer) { + if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x350); + } else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x980); + } else { + audio->externalMixing = false; + } + } info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED); info->reg = GBADMARegisterSetWidth(info->reg, 1); } @@ -265,23 +282,28 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { sampleLeft >>= psgShift; sampleRight >>= psgShift; - if (!audio->forceDisableChA) { - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; - } - - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; - } + if (audio->mixer) { + audio->mixer->step(audio->mixer); } + if (!audio->externalMixing) { + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } - if (!audio->forceDisableChB) { - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } + + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } } diff --git a/src/gba/core.c b/src/gba/core.c index 44d125f73..6b9061f8f 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #ifndef DISABLE_THREADING @@ -123,6 +124,9 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { }; struct mVideoLogContext; + +#define CPU_COMPONENT_AUDIO_MIXER CPU_COMPONENT_MISC_1 + struct GBACore { struct mCore d; struct GBAVideoSoftwareRenderer renderer; @@ -140,6 +144,7 @@ struct GBACore { const struct Configuration* overrides; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; + struct GBAAudioMixer* audioMixer; }; static bool _GBACoreInit(struct mCore* core) { @@ -162,6 +167,7 @@ static bool _GBACoreInit(struct mCore* core) { gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; gbacore->logContext = NULL; + gbacore->audioMixer = NULL; GBACreate(gba); // TODO: Restore cheats @@ -209,6 +215,7 @@ static void _GBACoreDeinit(struct mCore* core) { mCheatDeviceDestroy(gbacore->cheatDevice); } free(gbacore->cheatDevice); + free(gbacore->audioMixer); mCoreConfigFreeOpts(&core->opts); free(core); } @@ -272,6 +279,7 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections"); mCoreConfigCopyValue(&core->config, config, "gba.bios"); + mCoreConfigCopyValue(&core->config, config, "gba.audioHle"); #ifndef DISABLE_THREADING mCoreConfigCopyValue(&core->config, config, "threadedVideo"); @@ -464,6 +472,16 @@ static void _GBACoreReset(struct mCore* core) { GBAVideoAssociateRenderer(&gba->video, renderer); } +#ifndef MINIMAL_CORE + int useAudioMixer; + if (!gbacore->audioMixer && mCoreConfigGetIntValue(&core->config, "gba.audioHle", &useAudioMixer) && useAudioMixer) { + gbacore->audioMixer = malloc(sizeof(*gbacore->audioMixer)); + GBAAudioMixerCreate(gbacore->audioMixer); + ((struct ARMCore*) core->cpu)->components[CPU_COMPONENT_AUDIO_MIXER] = &gbacore->audioMixer->d; + ARMHotplugAttach(core->cpu, CPU_COMPONENT_AUDIO_MIXER); + } +#endif + GBAOverrideApplyDefaults(gba, gbacore->overrides); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 diff --git a/src/gba/extra/audio-mixer.c b/src/gba/extra/audio-mixer.c new file mode 100644 index 000000000..b7eb8c315 --- /dev/null +++ b/src/gba/extra/audio-mixer.c @@ -0,0 +1,304 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * 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 + +#include +#include +#include + +#define OVERSAMPLE 2 + +static void _mp2kInit(void* cpu, struct mCPUComponent* component); +static void _mp2kDeinit(struct mCPUComponent* component); + +static bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address); +static void _mp2kVblank(struct GBAAudioMixer* mixer); +static void _mp2kStep(struct GBAAudioMixer* mixer); + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer) { + mixer->d.init = _mp2kInit; + mixer->d.deinit = _mp2kDeinit; + mixer->engage = _mp2kEngage; + mixer->vblank = _mp2kVblank; + mixer->step = _mp2kStep; +} + +void _mp2kInit(void* cpu, struct mCPUComponent* component) { + struct ARMCore* arm = cpu; + struct GBA* gba = (struct GBA*) arm->master; + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + gba->audio.mixer = mixer; + mixer->p = &gba->audio; + mixer->contextAddress = 0; + mixer->tempo = 120.0 / 75.0; + mixer->frame = 0; + mixer->last.left = 0; + mixer->last.right = 0; + memset(&mixer->context, 0, sizeof(mixer->context)); + memset(&mixer->activeTracks, 0, sizeof(mixer->activeTracks)); + + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + mixer->activeTracks[i].channel = &mixer->context.chans[i]; + CircleBufferInit(&mixer->activeTracks[i].buffer, 0x10000); + } +} + +void _mp2kDeinit(struct mCPUComponent* component) { + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + CircleBufferDeinit(&mixer->activeTracks[i].buffer); + } +} + +static void _loadInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint32_t base) { + struct ARMMemory* memory = &cpu->memory; + instrument->type = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, type), 0); + instrument->key = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, key), 0); + instrument->length = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, length), 0); + instrument->ps.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, ps.pan), 0); + if (instrument->type == 0x40 || instrument->type == 0x80) { + instrument->data.subTable = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.subTable), 0); + instrument->extInfo.map = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.map), 0); + } else { + instrument->data.waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.waveData), 0); + instrument->extInfo.adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.attack), 0); + instrument->extInfo.adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.decay), 0); + instrument->extInfo.adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.sustain), 0); + instrument->extInfo.adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.release), 0); + } +} + +static void _lookupInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint8_t key) { + struct ARMMemory* memory = &cpu->memory; + if (instrument->type == 0x40) { + uint32_t subInstrumentBase = instrument->data.subTable; + uint32_t keyTable = instrument->extInfo.map; + uint8_t id = memory->load8(cpu, keyTable + key, 0); + subInstrumentBase += 12 * id; + _loadInstrument(cpu, instrument, subInstrumentBase); + } + if (instrument->type == 0x80) { + uint32_t subInstrumentBase = instrument->data.subTable; + subInstrumentBase += 12 * key; + _loadInstrument(cpu, instrument, subInstrumentBase); + } +} + +static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + uint32_t headerAddress; + struct GBAMP2kInstrument instrument = track->track.instrument; + + uint8_t note = track->track.key; + _lookupInstrument(cpu, &instrument, note); + double freq; + + switch (instrument.type) { + case 0x00: + case 0x08: + case 0x40: + case 0x80: + freq = GBA_ARM7TDMI_FREQUENCY / (double) track->channel->freq; + break; + default: + // We don't care about PSG channels + return; + } + headerAddress = instrument.data.waveData; + if (headerAddress < 0x20) { + mLOG(GBA_AUDIO, ERROR, "Audio track has invalid instrument"); + return; + } + uint32_t loopOffset = memory->load32(cpu, headerAddress + 0x8, 0); + uint32_t endOffset = memory->load32(cpu, headerAddress + 0xC, 0); + uint32_t sampleBase = headerAddress + 0x10; + uint32_t sampleI = track->samplePlaying; + double sampleOffset = track->currentOffset; + double updates = VIDEO_TOTAL_LENGTH / (mixer->tempo * mixer->p->sampleInterval / OVERSAMPLE); + int nSample; + for (nSample = 0; nSample < updates; ++nSample) { + int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0); + + struct GBAStereoSample stereo = { + (sample * track->channel->leftVolume * track->channel->envelopeV) >> 9, + (sample * track->channel->rightVolume * track->channel->envelopeV) >> 9 + }; + + CircleBufferWrite16(&track->buffer, stereo.left); + CircleBufferWrite16(&track->buffer, stereo.right); + + sampleOffset += mixer->p->sampleInterval / OVERSAMPLE; + while (sampleOffset > freq) { + sampleOffset -= freq; + ++sampleI; + if (sampleI >= endOffset) { + sampleI = loopOffset; + } + } + } + + track->samplePlaying = sampleI; + track->currentOffset = sampleOffset; +} + +static void _mp2kReload(struct GBAAudioMixer* mixer) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + mixer->context.magic = memory->load32(cpu, mixer->contextAddress + offsetof(struct GBAMP2kContext, magic), 0); + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kSoundChannel* ch = &mixer->context.chans[i]; + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + track->waiting = false; + uint32_t base = mixer->contextAddress + offsetof(struct GBAMP2kContext, chans[i]); + + ch->status = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, status), 0); + ch->type = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, type), 0); + ch->rightVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rightVolume), 0); + ch->leftVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, leftVolume), 0); + ch->adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.attack), 0); + ch->adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.decay), 0); + ch->adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.sustain), 0); + ch->adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.release), 0); + ch->ky = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ky), 0); + ch->envelopeV = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeV), 0); + ch->envelopeRight = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeRight), 0); + ch->envelopeLeft = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeLeft), 0); + ch->echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoVolume), 0); + ch->echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoLength), 0); + ch->d1 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d1), 0); + ch->d2 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d2), 0); + ch->gt = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, gt), 0); + ch->midiKey = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, midiKey), 0); + ch->ve = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ve), 0); + ch->pr = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, pr), 0); + ch->rp = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rp), 0); + ch->d3[0] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[0]), 0); + ch->d3[1] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[1]), 0); + ch->d3[2] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[2]), 0); + ch->ct = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, ct), 0); + ch->fw = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, fw), 0); + ch->freq = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, freq), 0); + ch->waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, waveData), 0); + ch->cp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, cp), 0); + ch->track = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, track), 0); + ch->pp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, pp), 0); + ch->np = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, np), 0); + ch->d4 = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, d4), 0); + ch->xpi = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpi), 0); + ch->xpc = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpc), 0); + + base = ch->track; + if (base) { + track->track.flags = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, flags), 0); + track->track.wait = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, wait), 0); + track->track.patternLevel = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternLevel), 0); + track->track.repN = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, repN), 0); + track->track.gateTime = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, gateTime), 0); + track->track.key = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, key), 0); + track->track.velocity = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, velocity), 0); + track->track.runningStatus = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, runningStatus), 0); + track->track.keyM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyM), 0); + track->track.pitM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitM), 0); + track->track.keyShift = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShift), 0); + track->track.keyShiftX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShiftX), 0); + track->track.tune = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, tune), 0); + track->track.pitX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitX), 0); + track->track.bend = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bend), 0); + track->track.bendRange = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bendRange), 0); + track->track.volMR = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volMR), 0); + track->track.volML = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volML), 0); + track->track.vol = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, vol), 0); + track->track.volX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volX), 0); + track->track.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pan), 0); + track->track.panX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, panX), 0); + track->track.modM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modM), 0); + track->track.mod = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, mod), 0); + track->track.modT = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modT), 0); + track->track.lfoSpeed = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeed), 0); + track->track.lfoSpeedC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeedC), 0); + track->track.lfoDelay = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelay), 0); + track->track.lfoDelayC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelayC), 0); + track->track.priority = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, priority), 0); + track->track.echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoVolume), 0); + track->track.echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoLength), 0); + track->track.chan = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, chan), 0); + _loadInstrument(cpu, &track->track.instrument, base + offsetof(struct GBAMP2kMusicPlayerTrack, instrument)); + track->track.cmdPtr = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, cmdPtr), 0); + track->track.patternStack[0] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[0]), 0); + track->track.patternStack[1] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[1]), 0); + track->track.patternStack[2] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[2]), 0); + } else { + memset(&track->track, 0, sizeof(track->track)); + } + } +} + +bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) { + if (address < BASE_WORKING_RAM) { + return false; + } + if (address != mixer->contextAddress) { + mixer->contextAddress = address; + mixer->p->externalMixing = true; + _mp2kReload(mixer); + } + return true; +} + +void _mp2kStep(struct GBAAudioMixer* mixer) { + if (!mixer->p->externalMixing) { + return; + } + mixer->frame += mixer->p->sampleInterval; + + while (mixer->frame >= VIDEO_TOTAL_LENGTH / mixer->tempo) { + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + if (track->channel->status > 0) { + _stepSample(mixer, track); + } else { + track->currentOffset = 0; + track->samplePlaying = 0; + CircleBufferClear(&track->buffer); + } + } + mixer->frame -= VIDEO_TOTAL_LENGTH / mixer->tempo; + } + + uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE; + int i; + for (i = 0; i < OVERSAMPLE; ++i) { + struct GBAStereoSample sample = {0}; + size_t track; + for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) { + if (!mixer->activeTracks[track].channel->status) { + continue; + } + int16_t value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.left += value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.right += value; + } + blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left); + blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left); + mixer->last = sample; + } +} + +void _mp2kVblank(struct GBAAudioMixer* mixer) { + if (!mixer->contextAddress) { + return; + } + mLOG(GBA_AUDIO, DEBUG, "Frame"); + mixer->p->externalMixing = true; + _mp2kReload(mixer); +} diff --git a/src/gba/gba.c b/src/gba/gba.c index 4434fc526..39a3f72c3 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -789,6 +789,10 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { void GBAFrameStarted(struct GBA* gba) { GBATestKeypadIRQ(gba); + if (gba->audio.mixer) { + gba->audio.mixer->vblank(gba->audio.mixer); + } + size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c); From 6fd97ce3de40a83a12c51622c58570dd7ecdd2e4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 May 2019 17:16:52 -0700 Subject: [PATCH 21/63] Qt: Enable XQ audio option --- src/platform/qt/SettingsView.cpp | 4 ++-- src/platform/qt/SettingsView.ui | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 95943c671..d5d0a91ac 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -401,7 +401,7 @@ void SettingsView::updateConfig() { saveSetting("logToStdout", m_ui.logToStdout); saveSetting("logFile", m_ui.logFile); saveSetting("useDiscordPresence", m_ui.useDiscordPresence); - saveSetting("audioHle", m_ui.audioHle); + saveSetting("gba.audioHle", m_ui.audioHle); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -550,7 +550,7 @@ void SettingsView::reloadConfig() { loadSetting("logToStdout", m_ui.logToStdout); loadSetting("logFile", m_ui.logFile); loadSetting("useDiscordPresence", m_ui.useDiscordPresence); - loadSetting("audioHle", m_ui.audioHle); + loadSetting("gba.audioHle", m_ui.audioHle); loadSetting("videoScale", m_ui.videoScale, 1); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 23172e691..1a7a9e83f 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -40,7 +40,7 @@ - 200 + 180 16777215 @@ -903,9 +903,6 @@ - - false - XQ GBA audio (experimental) From 6364cabce3ff683c7557c8e622152b897ea87928 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 18:38:40 -0700 Subject: [PATCH 22/63] GBA Audio: Default to emulated mixing for XCMD --- src/gba/extra/audio-mixer.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gba/extra/audio-mixer.c b/src/gba/extra/audio-mixer.c index b7eb8c315..85941536e 100644 --- a/src/gba/extra/audio-mixer.c +++ b/src/gba/extra/audio-mixer.c @@ -237,6 +237,10 @@ static void _mp2kReload(struct GBAAudioMixer* mixer) { } else { memset(&track->track, 0, sizeof(track->track)); } + if (track->track.runningStatus == 0xCD) { + // XCMD isn't supported + mixer->p->externalMixing = false; + } } } @@ -253,9 +257,6 @@ bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) { } void _mp2kStep(struct GBAAudioMixer* mixer) { - if (!mixer->p->externalMixing) { - return; - } mixer->frame += mixer->p->sampleInterval; while (mixer->frame >= VIDEO_TOTAL_LENGTH / mixer->tempo) { @@ -288,8 +289,10 @@ void _mp2kStep(struct GBAAudioMixer* mixer) { CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); sample.right += value; } - blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left); - blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left); + if (mixer->p->externalMixing) { + blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left); + blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left); + } mixer->last = sample; } } From 4a3d042089d0ce8c02cf8093105a4933e9770e62 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 18:40:35 -0700 Subject: [PATCH 23/63] CHANGES: Update --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index dbf38268f..a2f445615 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Features: - Debugger: Add tracing to file - Map viewer supports bitmapped GBA modes - OpenGL renderer with high-resolution upscaling support + - Experimental high level "XQ" audio for most GBA games Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs From c18c38b6169f7e7e52a0133a846929ceb3d88b0e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 19:06:10 -0700 Subject: [PATCH 24/63] GBA: Fix tests --- src/gba/test/cheats.c | 2 ++ src/gba/test/core.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index 6f4b8cb4c..527c3ccdc 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -16,6 +16,7 @@ static int cheatsSetup(void** state) { struct mCore* core = GBACoreCreate(); core->init(core); + mCoreInitConfig(core, NULL); core->cheatDevice(core); *state = core; return 0; @@ -26,6 +27,7 @@ static int cheatsTeardown(void** state) { return 0; } struct mCore* core = *state; + mCoreConfigDeinit(&core->config); core->deinit(core); return 0; } diff --git a/src/gba/test/core.c b/src/gba/test/core.c index acb0165af..9313010b6 100644 --- a/src/gba/test/core.c +++ b/src/gba/test/core.c @@ -27,7 +27,9 @@ M_TEST_DEFINE(reset) { struct mCore* core = GBACoreCreate(); assert_non_null(core); assert_true(core->init(core)); + mCoreInitConfig(core, NULL); core->reset(core); + mCoreConfigDeinit(&core->config); core->deinit(core); } @@ -36,7 +38,9 @@ M_TEST_DEFINE(loadNullROM) { assert_non_null(core); assert_true(core->init(core)); assert_false(core->loadROM(core, NULL)); + mCoreInitConfig(core, NULL); core->reset(core); + mCoreConfigDeinit(&core->config); core->deinit(core); } From 59db2a19460a8a1da529b8995de4adb5da9276fb Mon Sep 17 00:00:00 2001 From: EmpyreusX <36258024+EmpyreusX@users.noreply.github.com> Date: Mon, 20 May 2019 13:45:41 +0800 Subject: [PATCH 25/63] Update mgba-zh_CN.ts --- src/platform/qt/ts/mgba-zh_CN.ts | 298 +++++++++++++++++-------------- 1 file changed, 167 insertions(+), 131 deletions(-) diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index b60a570ad..faafd280a 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -4243,21 +4243,26 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 + Enhancements + 增强 + + + BIOS BIOS - + Paths 路径 - + Logging 日志记录 - + Game Boy Game Boy @@ -4272,491 +4277,522 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 音频缓冲: - - + + 1536 1536 - + 512 512 - + 768 768 - + 1024 1024 - + 2048 2048 - + 3072 3072 - + 4096 4096 - + samples 采样 - + Sample rate: 采样率: - - + + 44100 44100 - + 22050 22050 - + 32000 32000 - + 48000 48000 - + Hz Hz - + Volume: 音量: - - + + Mute 静音 - + Fast forward volume: 快进音量: - + Display driver: 显示驱动: - + Frameskip: 跳帧: - + Skip every 每间隔 - - + + frames - + FPS target: 目标 FPS: - + frames per second 帧每秒 - + Sync: 同步: - + Video 视频 - + Audio 音频 - + Lock aspect ratio 锁定纵横比 - + Force integer scaling 强制整数缩放 - + Bilinear filtering 双线性过滤 - + Language 语言 - + English 英语 - + Library: 库: - + List view 列表查看 - + Tree view 树状查看 - + Show when no game open 未打开游戏时显示 - + Clear cache 清除缓存 - + Allow opposing input directions 允许逆向输入 - + Suspend screensaver 停用屏幕保护程序 - + Pause when inactive 非活动时暂停 - + Show FPS in title bar 在标题栏显示 FPS - + Automatically save cheats 自动保存作弊码 - + Automatically load cheats 自动载入作弊码 - + Automatically save state 自动存档 - + Automatically load state 自动读档 - - + + Enable Discord Rich Presence 启用 Enable Discord Rich Presence - + Fast forward speed: 快进速度: - + + × × - + Unbounded 不限制 - + Autofire interval: 连发间隔: - + Enable rewind 启用回退 - + Rewind history: 回退历史: - + Idle loops: 空循环: - + Run all 运行所有 - + Remove known 移除选定 - + Detect and remove 检测并移除 - + Preload entire ROM into memory 将整个 ROM 预加载到内存中 - + Savestate extra data: 即时存档额外数据: - - + + Screenshot 截图 - - + + Save data 保存数据 - - + + Cheat codes 作弊码 - + Load extra data: - 读档时载入额外数据: + 载入额外数据: - - GB BIOS file: - GB BIOS 文件: + + Video renderer: + 视频渲染器: - - - - - - - - - + + Software + 软件 + + + + OpenGL + OpenGL + + + + OpenGL enhancements + OpenGL 增强 + + + + High-resolution scale: + 高分辨率比例: + + + + XQ GBA audio (experimental) + XQ GBA 音频 (实验) + + + + + + + + + + + Browse 浏览 - + + GB BIOS file: + GB BIOS 文件: + + + Use BIOS file if found 当可用时使用 BIOS 文件 - + Skip BIOS intro 跳过 BIOS 启动画面 - + GBA BIOS file: GBA BIOS 文件: - + GBC BIOS file: GBC BIOS 文件: - + SGB BIOS file: SGB BIOS 文件: - + Save games - 已保存的游戏 + 游戏存档 - - - - - + + + + + Same directory as the ROM 保存在 ROM 所在目录 Save states - 保存即时存档 + 即时存档 - + Screenshots 截图 - + Patches 补丁 - + Cheats 作弊码 - + Log to file 记录日志到文件 - + Log to console 记录日志到控制台 - + Select Log File 选择日志文件 - - Game Boy model - Game Boy 模型 + + Game Boy model: + Game Boy 模型: - - - + + + Autodetect 自动检测 - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model: Super Game Boy 模型: - + Game Boy Color model: Game Boy Color 模型: - + Default BG colors: 默认背景颜色: - + Super Game Boy borders Super Game Boy 边框 - + Camera driver: 相机驱动: - + Default sprite colors 1: 默认精灵图颜色 1: - + Default sprite colors 2: 默认精灵图颜色 2: - + Use GBC colors in GB games 在 GB 游戏中使用 GBC 颜色 - + Camera: 相机 From 19747ea21d890af09ce96dc45827543f8bdc4de0 Mon Sep 17 00:00:00 2001 From: Cameron Cawley Date: Fri, 26 Jan 2018 22:36:44 +0000 Subject: [PATCH 26/63] CMake: Improved detection of pthreads. --- CMakeLists.txt | 39 +++++++++++++++++--- include/mgba-util/platform/posix/threading.h | 13 ++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed522e70b..8ccebef30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,15 +257,10 @@ if(WIN32) endif() elseif(UNIX) set(USE_PTHREADS ON) - add_definitions(-DUSE_PTHREADS) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_definitions(-D_GNU_SOURCE) endif() - if(NOT APPLE AND NOT HAIKU) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") - endif() list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/posix/*.c) @@ -351,6 +346,7 @@ if(3DS OR WII) add_definitions(-D_GNU_SOURCE) endif() +include(CheckCCompilerFlag) include(CheckFunctionExists) include(CheckIncludeFiles) check_function_exists(strdup HAVE_STRDUP) @@ -402,6 +398,27 @@ endif() check_function_exists(chmod HAVE_CHMOD) check_function_exists(umask HAVE_UMASK) +if(USE_PTHREADS) + check_include_files("pthread.h" HAVE_PTHREAD_H) + if(HAVE_PTHREAD_H) + check_c_compiler_flag(-pthread HAVE_PTHREAD) + if(HAVE_PTHREAD AND NOT APPLE AND NOT HAIKU) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + endif() + + check_function_exists(pthread_create HAVE_PTHREAD_CREATE) + if(HAVE_PTHREAD_CREATE) + add_definitions(-DUSE_PTHREADS) + + check_include_files("pthread_np.h" HAVE_PTHREAD_NP_H) + + check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) + check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) + endif() + endif() +endif() + set(FUNCTION_DEFINES) if(HAVE_STRDUP) @@ -443,6 +460,18 @@ if(HAVE_UMASK) list(APPEND FUNCTION_DEFINES HAVE_UMASK) endif() +if(HAVE_PTHREAD_NP_H) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_NP_H) +endif() + +if(HAVE_PTHREAD_SETNAME_NP) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SETNAME_NP) +endif() + +if(HAVE_PTHREAD_SET_NAME_NP) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SET_NAME_NP) +endif() + # Feature dependencies set(FEATURE_DEFINES) set(FEATURE_FLAGS) diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index 468e1460c..38aac8f62 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -12,7 +12,7 @@ CXX_GUARD_START #include #include -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#ifdef HAVE_PTHREAD_NP_H #include #elif defined(__HAIKU__) #include @@ -85,20 +85,15 @@ static inline int ThreadJoin(Thread thread) { } static inline int ThreadSetName(const char* name) { -#ifdef __APPLE__ -#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 +#if defined(__APPLE__) && defined(HAVE_PTHREAD_SETNAME_NP) return pthread_setname_np(name); -#else - UNUSED(name); - return 0; -#endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_set_name_np(pthread_self(), name); return 0; #elif defined(__HAIKU__) rename_thread(find_thread(NULL), name); return 0; -#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old +#elif defined(HAVE_PTHREAD_SETNAME_NP) return pthread_setname_np(pthread_self(), name); #else UNUSED(name); From 109472f67f5a6019d994272b7cd5db31935294b6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 May 2019 15:05:58 -0700 Subject: [PATCH 27/63] GBA: Fix skipping BIOS on irregularly sized ROMs --- CHANGES | 1 + src/gba/core.c | 2 +- src/gba/gba.c | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index a2f445615..733f85d36 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,7 @@ Other fixes: - Qt: Fix some Qt display driver race conditions - FFmpeg: Fix audio conversion producing gaps - Core: Improved lockstep driver reliability (Le Hoang Quyen) + - GBA: Fix skipping BIOS on irregularly sized ROMs Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash diff --git a/src/gba/core.c b/src/gba/core.c index 6b9061f8f..e753971e6 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -528,7 +528,7 @@ static void _GBACoreReset(struct mCore* core) { #endif ARMReset(core->cpu); - if (core->opts.skipBios && gba->isPristine) { + if (core->opts.skipBios && (gba->romVf || gba->memory.rom)) { GBASkipBIOS(core->board); } } diff --git a/src/gba/gba.c b/src/gba/gba.c index 39a3f72c3..42697ffe1 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -241,10 +241,6 @@ void GBAReset(struct ARMCore* cpu) { if (gba->pristineRomSize > SIZE_CART0) { GBAMatrixReset(gba); } - - if (!gba->romVf && gba->memory.rom) { - GBASkipBIOS(gba); - } } void GBASkipBIOS(struct GBA* gba) { From d84793edeef33a3641ae8b6bbb9741e1cd29cd64 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 10:08:56 -0700 Subject: [PATCH 28/63] GBA Video: GL BG mosaic --- include/mgba/internal/gba/renderers/gl.h | 1 + src/gba/renderers/gl.c | 55 +++++++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 479a0b66f..c4081abbd 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -92,6 +92,7 @@ enum { GBA_GL_BG_INFLAGS, GBA_GL_BG_TRANSFORM, GBA_GL_BG_RANGE, + GBA_GL_BG_MOSAIC, GBA_GL_OBJ_VRAM = 2, GBA_GL_OBJ_PALETTE, diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index eef9a8fdf..541cdc528 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -104,6 +104,7 @@ static const struct GBAVideoGLUniform _uniformsMode0[] = { { "size", GBA_GL_BG_SIZE, }, { "offset", GBA_GL_BG_OFFSET, }, { "inflags", GBA_GL_BG_INFLAGS, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -116,6 +117,7 @@ static const char* const _renderMode0 = "uniform int size;\n" "uniform ivec2 offset;\n" "uniform ivec4 inflags;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" FLAG_CONST @@ -123,7 +125,14 @@ static const char* const _renderMode0 = "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" "void main() {\n" - " ivec2 coord = ivec2(texCoord) + offset;\n" + " ivec2 coord = ivec2(texCoord);\n" + " if (mosaic.x > 1) {\n" + " coord.x -= int(mod(coord.x, mosaic.x));\n" + " }\n" + " if (mosaic.y > 1) {\n" + " coord.y -= int(mod(coord.y, mosaic.y));\n" + " }\n" + " coord += offset;\n" " if ((size & 1) == 1) {\n" " coord.y += coord.x & 256;\n" " }\n" @@ -171,6 +180,7 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -194,6 +204,7 @@ static const char* const _renderMode2 = "uniform ivec2[4] offset;\n" "uniform ivec2[4] transform;\n" "uniform vec2 range;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" FLAG_CONST @@ -221,11 +232,18 @@ static const char* const _renderMode2 = "}\n" "void main() {\n" - " float y = texCoord.y - range.x;\n" + " vec2 coord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " coord.x -= mod(coord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " coord.y -= mod(coord.y, mosaic.y);\n" + " }\n" + " float y = coord.y - range.x;\n" " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n" + " color = fetchTile(ivec2(mixedTransform * coord.x + mixedOffset));\n" " flags = inflags / flagCoeff;\n" "}"; @@ -239,6 +257,7 @@ static const struct GBAVideoGLUniform _uniformsMode35[] = { { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -251,6 +270,7 @@ static const char* const _renderMode35 = "uniform ivec2[4] offset;\n" "uniform ivec2[4] transform;\n" "uniform vec2 range;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" FLAG_CONST @@ -260,11 +280,18 @@ static const char* const _renderMode35 = "vec2 interpolate(ivec2 arr[4], float x);\n" "void main() {\n" - " float y = texCoord.y - range.x;\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x -= mod(incoord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y -= mod(incoord.y, mosaic.y);\n" + " }\n" + " float y = incoord.y - range.x;\n" " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * texCoord.x + mixedOffset);\n" + " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" @@ -289,6 +316,7 @@ static const struct GBAVideoGLUniform _uniformsMode4[] = { { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -302,6 +330,7 @@ static const char* const _renderMode4 = "uniform ivec2[4] offset;\n" "uniform ivec2[4] transform;\n" "uniform vec2 range;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" FLAG_CONST @@ -311,11 +340,18 @@ static const char* const _renderMode4 = "vec2 interpolate(ivec2 arr[4], float x);\n" "void main() {\n" - " float y = texCoord.y - range.x;\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x -= mod(incoord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y -= mod(incoord.y, mosaic.y);\n" + " }\n" + " float y = incoord.y - range.x;\n" " float lin = 0.5 - y / range.y * 0.25;\n" " vec2 mixedTransform = interpolate(transform, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * texCoord.x + mixedOffset);\n" + " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" @@ -1284,6 +1320,11 @@ void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBa glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); + if (background->mosaic) { + glUniform2i(uniforms[GBA_GL_BG_MOSAIC], GBAMosaicControlGetBgV(renderer->mosaic), GBAMosaicControlGetBgH(renderer->mosaic)); + } else { + glUniform2i(uniforms[GBA_GL_BG_MOSAIC], 0, 0); + } glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), renderer->blda, 0); From 6eab8d3418507fb9491f7890357b9d2eaec57383 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 10:26:01 -0700 Subject: [PATCH 29/63] GBA Video: GL OBJ mosaic --- include/mgba/internal/gba/renderers/gl.h | 1 + src/gba/renderers/gl.c | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index c4081abbd..7abb46b06 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -103,6 +103,7 @@ enum { GBA_GL_OBJ_TRANSFORM, GBA_GL_OBJ_DIMS, GBA_GL_OBJ_OBJWIN, + GBA_GL_OBJ_MOSAIC, GBA_GL_FINALIZE_SCALE = 2, GBA_GL_FINALIZE_LAYERS, diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 541cdc528..fd316fece 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -378,6 +378,7 @@ static const struct GBAVideoGLUniform _uniformsObj[] = { { "transform", GBA_GL_OBJ_TRANSFORM, }, { "dims", GBA_GL_OBJ_DIMS, }, { "objwin", GBA_GL_OBJ_OBJWIN, }, + { "mosaic", GBA_GL_OBJ_MOSAIC, }, { 0 } }; @@ -392,6 +393,7 @@ static const char* const _renderObj = "uniform mat2x2 transform;\n" "uniform ivec4 dims;\n" "uniform vec4 objwin;\n" + "uniform ivec4 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" "out vec3 window;\n" @@ -400,7 +402,14 @@ static const char* const _renderObj = "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" "void main() {\n" - " ivec2 coord = ivec2(transform * (texCoord - dims.zw / 2) + dims.xy / 2);\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x -= mod(mosaic.z + incoord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y -= mod(mosaic.w + incoord.y, mosaic.y);\n" + " }\n" + " ivec2 coord = ivec2(transform * (incoord - dims.zw / 2) + dims.xy / 2);\n" " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" " discard;\n" " }\n" @@ -1305,6 +1314,11 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } + if (GBAObjAttributesAIsMosaic(sprite->a)) { + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], GBAMosaicControlGetObjV(renderer->mosaic), GBAMosaicControlGetObjH(renderer->mosaic), x, spriteY); + } else { + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], 0, 0, 0, 0); + } glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); From 29b2262e9a13ccc681e3ef74a18073571810125f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 11:20:00 -0700 Subject: [PATCH 30/63] Qt: Fix bounded fast forward with Qt Multimedia --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 8 ++++++++ src/platform/qt/Window.cpp | 1 + 3 files changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 733f85d36..d90047be2 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,7 @@ Other fixes: - FFmpeg: Fix audio conversion producing gaps - Core: Improved lockstep driver reliability (Le Hoang Quyen) - GBA: Fix skipping BIOS on irregularly sized ROMs + - Qt: Fix bounded fast forward with Qt Multimedia Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 2c6ec39f7..804d476d7 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -440,13 +440,21 @@ void CoreController::rewind(int states) { } void CoreController::setFastForward(bool enable) { + if (m_fastForward == enable) { + return; + } m_fastForward = enable; updateFastForward(); + emit fastForwardChanged(enable); } void CoreController::forceFastForward(bool enable) { + if (m_fastForwardForced == enable) { + return; + } m_fastForwardForced = enable; updateFastForward(); + emit fastForwardChanged(enable || m_fastForward); } void CoreController::loadState(int slot) { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 4498f8c5d..de4660f74 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -924,6 +924,7 @@ void Window::reloadAudioDriver() { m_audioProcessor->requestSampleRate(opts->sampleRate); m_audioProcessor->start(); connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop); + connect(m_controller.get(), &CoreController::fastForwardChanged, m_audioProcessor.get(), &AudioProcessor::inputParametersChanged); } void Window::tryMakePortable() { From 326a055b07d4e56d755090a75ad37c7ad9f39f7a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 11:35:34 -0700 Subject: [PATCH 31/63] GBA Video: Initialize and fix mosaic in GL --- src/gba/renderers/gl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index fd316fece..c21116409 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -751,6 +751,8 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) { glRenderer->paletteDirty = true; glRenderer->vramDirty = 0xFFFFFF; glRenderer->firstAffine = -1; + glRenderer->dispcnt = 0; + glRenderer->mosaic = 0; } void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { @@ -1315,7 +1317,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } if (GBAObjAttributesAIsMosaic(sprite->a)) { - glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], GBAMosaicControlGetObjV(renderer->mosaic), GBAMosaicControlGetObjH(renderer->mosaic), x, spriteY); + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], GBAMosaicControlGetObjV(renderer->mosaic) + 1, GBAMosaicControlGetObjH(renderer->mosaic) + 1, x, spriteY); } else { glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], 0, 0, 0, 0); } @@ -1335,7 +1337,7 @@ void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBa glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); if (background->mosaic) { - glUniform2i(uniforms[GBA_GL_BG_MOSAIC], GBAMosaicControlGetBgV(renderer->mosaic), GBAMosaicControlGetBgH(renderer->mosaic)); + glUniform2i(uniforms[GBA_GL_BG_MOSAIC], GBAMosaicControlGetBgV(renderer->mosaic) + 1, GBAMosaicControlGetBgH(renderer->mosaic) + 1); } else { glUniform2i(uniforms[GBA_GL_BG_MOSAIC], 0, 0); } From f781c793f508cb4d84ececcb697f4e2bfe3abc4e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 12:21:10 -0700 Subject: [PATCH 32/63] GBA Video: Use 1d texture for backdrop bits --- include/mgba/internal/gba/renderers/gl.h | 11 ++++--- src/gba/renderers/gl.c | 41 +++++++++++++++++------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 7abb46b06..6b10d2777 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -67,15 +67,18 @@ struct GBAVideoGLBackground { enum { GBA_GL_FBO_OBJ = 0, - GBA_GL_FBO_WINDOW = 1, - GBA_GL_FBO_OUTPUT = 2, + GBA_GL_FBO_BACKDROP, + GBA_GL_FBO_WINDOW, + GBA_GL_FBO_OUTPUT, GBA_GL_FBO_MAX }; enum { GBA_GL_TEX_OBJ_COLOR = 0, - GBA_GL_TEX_OBJ_FLAGS = 1, - GBA_GL_TEX_WINDOW = 6, + GBA_GL_TEX_OBJ_FLAGS, + GBA_GL_TEX_BACKDROP_COLOR, + GBA_GL_TEX_BACKDROP_FLAGS, + GBA_GL_TEX_WINDOW, GBA_GL_TEX_MAX }; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index c21116409..0822bc64c 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -440,8 +440,8 @@ static const char* const _finalize = "uniform sampler2D layers[5];\n" "uniform sampler2D flags[5];\n" "uniform sampler2D window;\n" - "uniform vec4 backdrop;\n" - "uniform vec4 backdropFlags;\n" + "uniform sampler2D backdrop;\n" + "uniform sampler2D backdropFlags;\n" FLAG_CONST "out vec4 color;\n" @@ -464,12 +464,12 @@ static const char* const _finalize = "}\n" "void main() {\n" + " vec4 topPixel = texelFetch(backdrop, ivec2(0, texCoord.y), 0);\n" + " vec4 bottomPixel = topPixel;\n" + " ivec4 topFlags = ivec4(texelFetch(backdropFlags, ivec2(0, texCoord.y), 0) * flagCoeff);\n" + " ivec4 bottomFlags = topFlags;\n" " vec4 windowFlags = texelFetch(window, ivec2(texCoord * scale), 0);\n" " int layerWindow = int(windowFlags.x * 128);\n" - " vec4 topPixel = backdrop;\n" - " vec4 bottomPixel = backdrop;\n" - " ivec4 topFlags = ivec4(backdropFlags * flagCoeff);\n" - " ivec4 bottomFlags = ivec4(backdropFlags * flagCoeff);\n" " if ((layerWindow & 1) == 0) {\n" " vec4 pix = texelFetch(layers[0], ivec2(texCoord * scale), 0);\n" " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" @@ -585,7 +585,7 @@ static void _initFramebufferTexture(GLuint tex, GLenum format, GLenum attachment glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, format, GBA_VIDEO_HORIZONTAL_PIXELS * scale, GBA_VIDEO_VERTICAL_PIXELS * scale, 0, format, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, format, scale > 0 ? GBA_VIDEO_HORIZONTAL_PIXELS * scale : 1, GBA_VIDEO_VERTICAL_PIXELS * (scale > 0 ? scale : 1), 0, format, GL_UNSIGNED_BYTE, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex, 0); } @@ -612,6 +612,10 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGBA, GL_COLOR_ATTACHMENT2, glRenderer->scale); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_COLOR], GL_RGB, GL_COLOR_ATTACHMENT0, 0); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, 0); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); @@ -1015,10 +1019,21 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glClear(GL_COLOR_BUFFER_BIT); } - glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } glEnable(GL_SCISSOR_TEST); + uint32_t backdrop = M_RGB5_TO_RGB8(glRenderer->d.palette[0]); + glViewport(0, 0, 1, GBA_VIDEO_VERTICAL_PIXELS); + glScissor(0, y, 1, 1); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); + glClearColor(((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); + glClear(GL_COLOR_BUFFER_BIT); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT1 }); + glClearColor(1, (glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4)) / 32.f, glRenderer->blda / 16.f, 0); + glClear(GL_COLOR_BUFFER_BIT); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); + if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { if (glRenderer->firstAffine < 0) { memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); @@ -1235,16 +1250,20 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glBindTexture(GL_TEXTURE_2D, renderer->bg[3].tex); glActiveTexture(GL_TEXTURE0 + 10); glBindTexture(GL_TEXTURE_2D, renderer->bg[3].flags); + glActiveTexture(GL_TEXTURE0 + 11); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_COLOR]); + glActiveTexture(GL_TEXTURE0 + 12); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_FLAGS]); - uint32_t backdrop = M_RGB5_TO_RGB8(renderer->d.palette[0]); glUniform2i(uniforms[GBA_GL_VS_LOC], GBA_VIDEO_VERTICAL_PIXELS, 0); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_FINALIZE_SCALE], renderer->scale); glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 5, 7, 9, 1 }); glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, renderer->blda / 16.f, 0); + glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); + glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 11); + glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 12); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); From 53d9e6b43213d9eed2c7bc5b38d16f4fdad37bc3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 13:15:17 -0700 Subject: [PATCH 33/63] GBA Video: Batch VRAM upload --- src/gba/renderers/gl.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 0822bc64c..e7941f161 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -994,13 +994,17 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glRenderer->paletteDirty = false; } int i; - for (i = 0; i < 24; ++i) { + int first = -1; + glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); + for (i = 0; i < 25; ++i) { if (!(glRenderer->vramDirty & (1 << i))) { - continue; + if (first >= 0) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * first, 256, 8 * (i - first), GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vram[2048 * first]); + first = -1; + } + } else if (first < 0) { + first = i; } - // TODO: PBOs - glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * i, 256, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vram[2048 * i]); } glRenderer->vramDirty = 0; From eeee6fe44eb11e77774c7bb3f08c2016682d33cd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 14:42:55 -0700 Subject: [PATCH 34/63] Qt: Fix GL deadlocks --- src/feature/thread-proxy.c | 2 ++ src/gb/extra/proxy.c | 13 ------------- src/gba/extra/proxy.c | 10 ---------- src/gba/video.c | 3 +-- src/platform/qt/CoreController.cpp | 1 - src/platform/qt/DisplayGL.cpp | 6 +++--- src/platform/qt/VideoProxy.cpp | 8 ++++++++ src/platform/qt/VideoProxy.h | 2 +- src/platform/qt/Window.cpp | 2 +- 9 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c index 7a67dbedd..6dca713ca 100644 --- a/src/feature/thread-proxy.c +++ b/src/feature/thread-proxy.c @@ -153,10 +153,12 @@ static void _wait(struct mVideoLogger* logger) { _proxyThreadRecover(proxyRenderer); return; } + MutexLock(&proxyRenderer->mutex); while (RingFIFOSize(&proxyRenderer->dirtyQueue)) { ConditionWake(&proxyRenderer->toThreadCond); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); } + MutexUnlock(&proxyRenderer->mutex); } static void _unlock(struct mVideoLogger* logger) { diff --git a/src/gb/extra/proxy.c b/src/gb/extra/proxy.c index d8538124d..03119226e 100644 --- a/src/gb/extra/proxy.c +++ b/src/gb/extra/proxy.c @@ -250,31 +250,21 @@ void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y) void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer) { struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); - } if (!proxyRenderer->logger->block) { proxyRenderer->backend->finishFrame(proxyRenderer->backend); } mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBVideoProxyRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) { struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); // Insert an extra item into the queue to make sure it gets flushed mVideoLoggerRendererFlush(proxyRenderer->logger); proxyRenderer->logger->wait(proxyRenderer->logger); } proxyRenderer->backend->enableSGBBorder(proxyRenderer->backend, enable); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -297,9 +287,6 @@ static void GBVideoProxyRendererPutPixels(struct GBVideoRenderer* renderer, size struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - // Insert an extra item into the queue to make sure it gets flushed - mVideoLoggerRendererFlush(proxyRenderer->logger); - proxyRenderer->logger->wait(proxyRenderer->logger); } proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index bd90f1271..3015b14ce 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -289,28 +289,20 @@ void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); - } if (!proxyRenderer->logger->block) { proxyRenderer->backend->finishFrame(proxyRenderer->backend); } mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); // Insert an extra item into the queue to make sure it gets flushed mVideoLoggerRendererFlush(proxyRenderer->logger); proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS); mVideoLoggerRendererFlush(proxyRenderer->logger); - proxyRenderer->logger->unlock(proxyRenderer->logger); *pixels = proxyRenderer->logger->pixelBuffer; *stride = proxyRenderer->logger->pixelStride; } else { @@ -322,8 +314,6 @@ static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, si struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - // Insert an extra item into the queue to make sure it gets flushed - mVideoLoggerRendererFlush(proxyRenderer->logger); } proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { diff --git a/src/gba/video.c b/src/gba/video.c index e75d255b1..7660e0b04 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -98,8 +98,7 @@ void GBAVideoReset(struct GBAVideo* video) { memset(video->palette, 0, sizeof(video->palette)); memset(video->oam.raw, 0, sizeof(video->oam.raw)); - video->renderer->deinit(video->renderer); - video->renderer->init(video->renderer); + video->renderer->reset(video->renderer); } void GBAVideoDeinit(struct GBAVideo* video) { diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 804d476d7..10d08bb3c 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -363,7 +363,6 @@ void CoreController::stop() { #endif setPaused(false); mCoreThreadEnd(&m_threadContext); - emit stopping(); } void CoreController::reset() { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 1d1e79424..dc360cdb0 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -59,9 +59,6 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_painter = new PainterGL(&m_videoProxy, windowHandle(), m_gl); setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions - - connect(&m_videoProxy, &VideoProxy::dataAvailable, &m_videoProxy, &VideoProxy::processData); - connect(&m_videoProxy, &VideoProxy::eventPosted, &m_videoProxy, &VideoProxy::handleEvent); } DisplayGL::~DisplayGL() { @@ -417,6 +414,9 @@ void PainterGL::stop() { dequeueAll(); m_backend->clear(m_backend); m_backend->swap(m_backend); + if (m_videoProxy) { + m_videoProxy->reset(); + } m_gl->doneCurrent(); m_gl->moveToThread(m_surface->thread()); m_context.reset(); diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index a6c673207..1eb8fa933 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -24,6 +24,9 @@ VideoProxy::VideoProxy() { m_logger.d.writeData = &callback::func<&VideoProxy::writeData>; m_logger.d.readData = &callback::func<&VideoProxy::readData>; m_logger.d.postEvent = &callback::func<&VideoProxy::postEvent>; + + connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); + connect(this, &VideoProxy::eventPosted, this, &VideoProxy::handleEvent); } void VideoProxy::attach(CoreController* controller) { @@ -41,7 +44,10 @@ void VideoProxy::init() { } void VideoProxy::reset() { + m_mutex.lock(); RingFIFOClear(&m_dirtyQueue); + m_toThreadCond.wakeAll(); + m_mutex.unlock(); } void VideoProxy::deinit() { @@ -92,11 +98,13 @@ void VideoProxy::unlock() { } void VideoProxy::wait() { + m_mutex.lock(); while (RingFIFOSize(&m_dirtyQueue)) { emit dataAvailable(); m_toThreadCond.wakeAll(); m_fromThreadCond.wait(&m_mutex, 1); } + m_mutex.unlock(); } void VideoProxy::wake(int y) { diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h index 7f778a605..e62facca1 100644 --- a/src/platform/qt/VideoProxy.h +++ b/src/platform/qt/VideoProxy.h @@ -30,11 +30,11 @@ signals: public slots: void processData(); + void reset(); void handleEvent(int); private: void init(); - void reset(); void deinit(); bool writeData(const void* data, size_t length); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index de4660f74..778102edc 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -726,7 +726,6 @@ void Window::gameStarted() { menuBar()->hide(); } #endif - m_display->startDrawing(m_controller); reloadAudioDriver(); multiplayerChanged(); @@ -1733,6 +1732,7 @@ void Window::setController(CoreController* controller, const QString& fname) { m_inputController.recalibrateAxes(); m_controller->setInputController(&m_inputController); m_controller->setLogger(&m_log); + m_display->startDrawing(m_controller); connect(this, &Window::shutdown, [this]() { if (!m_controller) { From bda0f67103dfb316b6da67c4172e52a9e42d9ec1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 15:31:47 -0700 Subject: [PATCH 35/63] Qt: Fix shutdown crashes --- src/gba/extra/proxy.c | 1 + src/platform/qt/CoreController.cpp | 3 --- src/platform/qt/Window.cpp | 18 +++++++++++++----- src/platform/qt/Window.h | 1 + 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index 3015b14ce..52a0047b4 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -133,6 +133,7 @@ void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) { proxyRenderer->backend->deinit(proxyRenderer->backend); } else { proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT); + mVideoLoggerRendererFlush(proxyRenderer->logger); } mVideoLoggerRendererDeinit(proxyRenderer->logger); diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 10d08bb3c..bd12de40d 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -196,9 +196,6 @@ CoreController::~CoreController() { mCacheSetDeinit(m_cacheSet.get()); m_cacheSet.reset(); } - - mCoreConfigDeinit(&m_threadContext.core->config); - m_threadContext.core->deinit(m_threadContext.core); } const color_t* CoreController::drawContext() { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 778102edc..335149cf3 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -631,8 +631,10 @@ void Window::closeEvent(QCloseEvent* event) { m_config->setOption("width", GBA_VIDEO_HORIZONTAL_PIXELS * m_savedScale); } saveConfig(); - m_display.reset(); - QMainWindow::closeEvent(event); + if (m_controller) { + event->ignore(); + m_pendingClose = true; + } } void Window::focusInEvent(QFocusEvent*) { @@ -776,6 +778,11 @@ void Window::gameStarted() { void Window::gameStopped() { m_controller.reset(); + m_display->stopDrawing(); + if (m_pendingClose) { + m_display.reset(); + close(); + } #ifdef M_CORE_GBA for (Action* action : m_platformActions) { action->setEnabled(true); @@ -863,7 +870,6 @@ void Window::reloadDisplayDriver() { m_shaderView = std::make_unique(m_display.get(), m_config); #endif - connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing); connect(m_display.get(), &Display::hideCursor, [this]() { if (static_cast(m_screenWidget->layout())->currentWidget() == m_display.get()) { m_screenWidget->setCursor(Qt::BlankCursor); @@ -888,7 +894,6 @@ void Window::reloadDisplayDriver() { #endif if (m_controller) { - connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); @@ -1700,6 +1705,9 @@ void Window::setController(CoreController* controller, const QString& fname) { if (!controller) { return; } + if (m_pendingClose) { + return; + } if (m_controller) { m_controller->stop(); @@ -1739,6 +1747,7 @@ void Window::setController(CoreController* controller, const QString& fname) { return; } m_controller->stop(); + disconnect(m_controller.get(), &CoreController::started, this, &Window::gameStarted); }); connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted); @@ -1766,7 +1775,6 @@ void Window::setController(CoreController* controller, const QString& fname) { emit paused(false); }); - connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 01d036d32..526a10496 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -209,6 +209,7 @@ private: QString m_pendingPatch; QString m_pendingState; bool m_pendingPause = false; + bool m_pendingClose = false; bool m_hitUnimplementedBiosCall; From 3056655060b3be0eabd35742ea2b8e1cb18ccb09 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 17:28:08 -0700 Subject: [PATCH 36/63] GBA Video: Fix sprite mosaic clamping (fixes #1008) --- CHANGES | 1 + .../gba/obj/mosaic-height/baseline_0000.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0001.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0002.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0003.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0004.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0005.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0006.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0007.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0008.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0009.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0010.png | Bin 0 -> 1689 bytes .../gba/obj/mosaic-height/baseline_0011.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0012.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0013.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0014.png | Bin 0 -> 1536 bytes .../gba/obj/mosaic-height/baseline_0015.png | Bin 0 -> 1471 bytes .../gba/obj/mosaic-height/baseline_0016.png | Bin 0 -> 1471 bytes cinema/gba/obj/mosaic-height/test.mvl | Bin 0 -> 45041 bytes .../zmc-window-mosaic/baseline_0001.png | Bin 10115 -> 10189 bytes .../zmc-window-mosaic/baseline_0002.png | Bin 10179 -> 10265 bytes .../zmc-window-mosaic/baseline_0003.png | Bin 7843 -> 8022 bytes .../zmc-window-mosaic/baseline_0004.png | Bin 7743 -> 7925 bytes .../zmc-window-mosaic/baseline_0005.png | Bin 6117 -> 6207 bytes .../zmc-window-mosaic/baseline_0006.png | Bin 6073 -> 6158 bytes .../zmc-window-mosaic/baseline_0007.png | Bin 5388 -> 5573 bytes .../zmc-window-mosaic/baseline_0008.png | Bin 5362 -> 5556 bytes .../zmc-window-mosaic/baseline_0009.png | Bin 4811 -> 5032 bytes .../zmc-window-mosaic/baseline_0010.png | Bin 4851 -> 5062 bytes .../zmc-window-mosaic/baseline_0011.png | Bin 4434 -> 4548 bytes .../zmc-window-mosaic/baseline_0012.png | Bin 4409 -> 4518 bytes .../zmc-window-mosaic/baseline_0013.png | Bin 3886 -> 3983 bytes .../zmc-window-mosaic/baseline_0014.png | Bin 3863 -> 3958 bytes .../zmc-window-mosaic/baseline_0015.png | Bin 3858 -> 4054 bytes .../zmc-window-mosaic/baseline_0016.png | Bin 3780 -> 3982 bytes .../zmc-window-mosaic/baseline_0017.png | Bin 3565 -> 3737 bytes .../zmc-window-mosaic/baseline_0018.png | Bin 3447 -> 3618 bytes .../zmc-window-mosaic/baseline_0019.png | Bin 3342 -> 3532 bytes .../zmc-window-mosaic/baseline_0020.png | Bin 3262 -> 3448 bytes .../zmc-window-mosaic/baseline_0021.png | Bin 3147 -> 3284 bytes .../zmc-window-mosaic/baseline_0022.png | Bin 3103 -> 3246 bytes .../zmc-window-mosaic/baseline_0023.png | Bin 2903 -> 3113 bytes .../zmc-window-mosaic/baseline_0024.png | Bin 2826 -> 2999 bytes .../zmc-window-mosaic/baseline_0025.png | Bin 2721 -> 2840 bytes .../zmc-window-mosaic/baseline_0026.png | Bin 2516 -> 2620 bytes .../zmc-window-mosaic/baseline_0027.png | Bin 2301 -> 2361 bytes src/gba/renderers/software-obj.c | 15 ++++----------- src/gba/renderers/video-software.c | 12 +++++++++--- 48 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 cinema/gba/obj/mosaic-height/baseline_0000.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0001.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0002.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0003.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0004.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0005.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0006.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0007.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0008.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0009.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0010.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0011.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0012.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0013.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0014.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0015.png create mode 100644 cinema/gba/obj/mosaic-height/baseline_0016.png create mode 100644 cinema/gba/obj/mosaic-height/test.mvl diff --git a/CHANGES b/CHANGES index d90047be2..fa64821a2 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Emulation fixes: - GBA Memory: Fix writing to OBJ memory in modes 3 and 5 - GBA: Fix RTC on non-standard sized ROMs (fixes mgba.io/i/1400) - GBA Memory: Prevent writing to mirrored BG VRAM (fixes mgba.io/i/743) + - GBA Video: Fix sprite mosaic clamping (fixes mgba.io/i/1008) Other fixes: - Qt: More app metadata fixes - Qt: Fix load recent from archive (fixes mgba.io/i/1325) diff --git a/cinema/gba/obj/mosaic-height/baseline_0000.png b/cinema/gba/obj/mosaic-height/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0001.png b/cinema/gba/obj/mosaic-height/baseline_0001.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0002.png b/cinema/gba/obj/mosaic-height/baseline_0002.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0003.png b/cinema/gba/obj/mosaic-height/baseline_0003.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0004.png b/cinema/gba/obj/mosaic-height/baseline_0004.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0005.png b/cinema/gba/obj/mosaic-height/baseline_0005.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0006.png b/cinema/gba/obj/mosaic-height/baseline_0006.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0007.png b/cinema/gba/obj/mosaic-height/baseline_0007.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0008.png b/cinema/gba/obj/mosaic-height/baseline_0008.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0009.png b/cinema/gba/obj/mosaic-height/baseline_0009.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0010.png b/cinema/gba/obj/mosaic-height/baseline_0010.png new file mode 100644 index 0000000000000000000000000000000000000000..448ac5ceec0bfaa3f7912b2fc321cc25506cf34d GIT binary patch literal 1689 zcmaKtdpHwn9LM)`)IuvYQ7&DMvdkqUg zDGr&8Ll0*S4XZ=Vq_Rw!>((l_v*(^o53{5G;;Us zFf%sp&NnhJNk=PkA zn~Xy$AE0U~KkUBGonQvX(x5<>WQ+Lk$q5tLy)eJ2$tn-RxK-_M(LhMLVbj|2y9T;h=P~b4* z8B)KbEL3=g>;L9CRzcPYA@`ly-Q7KYTz67;k4a2)|V0u9d#GOYvP=D|sW zv9lSgyYi)0&W9}Wd@OThFi3|0P8}WmP*Hei0F_O1)}=wk=@mAnt$QJ;Ok02bvu~-{ z{SP4RlOU4PwDGw12CkG*JiccCQI{uVUx1R)`S6JuM1b56Yq* zKT}z=JmPEMmF}$loK~(p<-38X(f7O>Xm85(JGV{}$rJ5`Ekfp;)+qCX z-8Z!Tt1n!5*5XgP>MT@n?l)CNJ_q3!%7F{%qWF7pD|;zyZ{wbAAyQ6Ad|^y$&(tH4 zxL}l7F`>Lm@F=M?;0t+CK>Q^mD$UD(|RX~tP+x9BYJ{> zuPNHHm|T`xKyAWZ&6`Yg$t%pfzbX7wytdWoY8(KVfvp;ER_j{yFp#HrU}* z>ckDXSQ@`6Tm$)h6B-ekABliXL(p+GEm`N#bPhl0Pc)<_k%4jh?R0kSa)b8SBCDdXK^Z979JAtd z6};ZgWl2Ghhh@NJxZ`b~l-ERC3kX(eSFvr#3HDv0d+-KKH1KH_;XdXPS7ZZJjB75V zLQvL6)ajC$J@@rs>EW*|BM6nc@d9w ztsPxySk#S5K9u{~tbIj)2t5AA!3>oK-mp^Jcu}A>_AR|amrT|2{Ff|G9r>37fSo*J J&#=R!{SEFi6E^?= literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0011.png b/cinema/gba/obj/mosaic-height/baseline_0011.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0012.png b/cinema/gba/obj/mosaic-height/baseline_0012.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0013.png b/cinema/gba/obj/mosaic-height/baseline_0013.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0014.png b/cinema/gba/obj/mosaic-height/baseline_0014.png new file mode 100644 index 0000000000000000000000000000000000000000..7f04ae705518fe3936dd81d2d08c954a072d7841 GIT binary patch literal 1536 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0h7r;B4q#hkZy6^n02NVF!F zsyifJKhxBtmmR^&yjf>MYvi;|VtP8fJUls(hov%JY?!rn;leXIX@~!^uTNj{b(?La z{I6csn|8`Y&+h$x_36_#saLQ6docQuBIFt62Miwfk~vPdGs)$l@B(gM+^IcM76%kSg+_a<+9`^(S9TYHk% z*<{A?@7q*YyKkZBQC#{`LxO*NaasM{Y_|H0YfBG&x$wPt-ilSLUj4eu>+^fwyq)W~ z{my4bT{XD-t^MG4+qt`zrQ6B8n0Y*AX7A4R`7q0|m@au$#OPgD+FPDwtFL~3pnd(h zN$ur*jhX$m_v+k(-!Xa5JoM$d$+EY}cUY&*Dw}Q_vL)?`Ig)qKO`NkqbI#cnlckgG z<{#dYdHR9&d-E#^va24(Cd6K>a>?5tH_LE;8$S0~CI(knUDhluy1T6XWukqOyYHR& zDeG^`mo>k)&VF(0?V8q?NuJ-IZmmuJruOuFT&$VF-8JSWcSUFXYQq-X5cf@)e@(=U z_pQOssJXB9iHmP}{gUtZo<6QMuNtZ+PvhK{x1;0T>Fw+@?%lt8xn290Tk^M`GMmj` zK31NacyifgZ~R6Jo3+;NZ`Cw!?Frxf)mF4Bdd=@wyADjL-pUm&^X-Q6IsF%#Cx6zS zQMQ|J*`Mf#%JMf2r+$<=bx1dPVQseU@&Nq~tjPrE?o*tzo$R)+f8F-}*vVzQZ;##d z;r6L~9%Po*d$(UzchR01!Nu2nGStuSlKW>Z9~pc?UH{vc-dWQ&ZM@HKfB7PzNb*_n z{^r|)!@fu6#_V65*R%Y1*<+cyV~Vc}pWc6dzT9B%u74Ik_PO7(d-J64OUDkq4Y@Dn zWBx7p-|@fpJ~$ntM?55f7adwwuz6vvUAnQA{)F?Ick(jhFH2oFn#TD~!D`Pwo2GI` z-OJkz=AM6f%68p7?Hf0G58m(iz31p7$z?S{itn>uNuy>K6ifVWh?L3Boqya{akk$| z?ltFDa@!nd%kE#zre4vds267Q=sZO}E^|#-m*rkQ{H5v}=dxP=r_Yxyu79|4asHPr3-k*1FP`=>*H@db))_}Z z0E%JB^?zdpr?|$K+ZNAzd7`%bO0R9qAJ)sOZ|57t{*kyhXDe@PrP0Ev z^>+fq-o)IVRuya~U$}c|G1twMBEDdFGC0Lq{-Hke+79j7pZnz=w|+)*!>3ojW_=Tn zT_0}nblSvlZKHig)^}_D%by0lcye^#@@?;LR8F{Z&i4Iy=N8 M)78&qol`;+0L`!IIsgCw literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0015.png b/cinema/gba/obj/mosaic-height/baseline_0015.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9d0e36129dd108825b93add57273e6c11e1a14 GIT binary patch literal 1471 zcmai!eK6a19LLk9Jx?PoPRzsasJawctj@|2hSAn@P!B~Y7FJIYC6Z=A>^ee2uWYH+ zLpE9}o-#ubNm_~6bRO2BsvZ_{DrkgeNQ347UFz=j{e1rW-skhWdw-~?pewrCCfXnn zNZ0qOcc`jwsUAb~q^b_~;T=Gr)8G4gdtM{dY|dL|%_SQT<1kQ$L$v^fO3+JVFoejS zXU}<6n9ngbF;TiHKX!g<@*19e*0HKu0H3D(B_`u{iPx1YM-Alh(ECUGw|5nl!4ie7 z|E*hS80Yzod#aK-BLtU;!;qbu?&lZ43q|#ZQd5u~m632q4PwUw>;6nA#86-X7`L2w z(lREZ%(8vir^-)CCpx{FuqsEj?YkQ|b}26zwe`$cfz41SUY>NT&=Y1-;aiKr8xiVgm_> zKv;6WAhQh9Qu=16D*}Sujob^ACXl=9F2G1Bl(b}S5}~P{1-@j!mPuf&_sDp0897}+ zN)i{^(HU1px}eQ6qx&zf=fHVQ1d+EZ14Vq)daJ8USoIAcZt7R+A3L4I%ISQDwEIyh=m z3XZx&eS3`Cv>HXkhv-0{88Ov|+vn{(o-K()Ez0Mm0@fDNpu@sxZdB|57zgtuG55>T zd=cZ!P~@wFwd)(4{dO!H`Vrw+5oI;>W$Q9UB02}W!iG8ZXLr?Non03X&j;n@^~!iN z0yGkZ;hMk0A6CcK6%3pO2U-D2K>6Sp1#90l0>NnNBbMXCM|I(ubC-Z-VI{%hfU?@) zw=ujEV5WnOVVT9h5KO**)>)!aO502NfnIAol3%WYca?ul$c5|(iO31L2r9qW5wCz9 zlZhO6C~3q8rl-FrbyF56t7K^eCsqgG1pmov=io?2r|@Y}5n}XH)F! zK8cU8N6-r1_Am(^?m2@%ya3$h@=N`8?dmbR`J&6v&DRV?uI&$L(QCGYukTDJ$v&H1 zyOes7+#AIQH%J$kDcuS;@*$d>M1+l{`(tdz(xrHj>8M!YhTcjjT_zMAg*>e-sYpe# z$H;7~k`7jzgdpHhX%xKiZ{mD^G&Ov63MO;hF6Qz*MN@ueKAb)y7%fF&Ip-6?2QMoe zfT#7P;Y(sM5sA_h6l*pH+5!CtT8k|Z#jF6mSQS};@_JQaNwg`AM%4dbs~F4LbNTyp TWwSzcm_fciLEi0NH;VoP|D&tG literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/baseline_0016.png b/cinema/gba/obj/mosaic-height/baseline_0016.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9d0e36129dd108825b93add57273e6c11e1a14 GIT binary patch literal 1471 zcmai!eK6a19LLk9Jx?PoPRzsasJawctj@|2hSAn@P!B~Y7FJIYC6Z=A>^ee2uWYH+ zLpE9}o-#ubNm_~6bRO2BsvZ_{DrkgeNQ347UFz=j{e1rW-skhWdw-~?pewrCCfXnn zNZ0qOcc`jwsUAb~q^b_~;T=Gr)8G4gdtM{dY|dL|%_SQT<1kQ$L$v^fO3+JVFoejS zXU}<6n9ngbF;TiHKX!g<@*19e*0HKu0H3D(B_`u{iPx1YM-Alh(ECUGw|5nl!4ie7 z|E*hS80Yzod#aK-BLtU;!;qbu?&lZ43q|#ZQd5u~m632q4PwUw>;6nA#86-X7`L2w z(lREZ%(8vir^-)CCpx{FuqsEj?YkQ|b}26zwe`$cfz41SUY>NT&=Y1-;aiKr8xiVgm_> zKv;6WAhQh9Qu=16D*}Sujob^ACXl=9F2G1Bl(b}S5}~P{1-@j!mPuf&_sDp0897}+ zN)i{^(HU1px}eQ6qx&zf=fHVQ1d+EZ14Vq)daJ8USoIAcZt7R+A3L4I%ISQDwEIyh=m z3XZx&eS3`Cv>HXkhv-0{88Ov|+vn{(o-K()Ez0Mm0@fDNpu@sxZdB|57zgtuG55>T zd=cZ!P~@wFwd)(4{dO!H`Vrw+5oI;>W$Q9UB02}W!iG8ZXLr?Non03X&j;n@^~!iN z0yGkZ;hMk0A6CcK6%3pO2U-D2K>6Sp1#90l0>NnNBbMXCM|I(ubC-Z-VI{%hfU?@) zw=ujEV5WnOVVT9h5KO**)>)!aO502NfnIAol3%WYca?ul$c5|(iO31L2r9qW5wCz9 zlZhO6C~3q8rl-FrbyF56t7K^eCsqgG1pmov=io?2r|@Y}5n}XH)F! zK8cU8N6-r1_Am(^?m2@%ya3$h@=N`8?dmbR`J&6v&DRV?uI&$L(QCGYukTDJ$v&H1 zyOes7+#AIQH%J$kDcuS;@*$d>M1+l{`(tdz(xrHj>8M!YhTcjjT_zMAg*>e-sYpe# z$H;7~k`7jzgdpHhX%xKiZ{mD^G&Ov63MO;hF6Qz*MN@ueKAb)y7%fF&Ip-6?2QMoe zfT#7P;Y(sM5sA_h6l*pH+5!CtT8k|Z#jF6mSQS};@_JQaNwg`AM%4dbs~F4LbNTyp TWwSzcm_fciLEi0NH;VoP|D&tG literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/mosaic-height/test.mvl b/cinema/gba/obj/mosaic-height/test.mvl new file mode 100644 index 0000000000000000000000000000000000000000..3ab153bb2989077a30ddf3b3993c9af432c8721c GIT binary patch literal 45041 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 Us`!3$IFI0siq=c`3!4xB1MGy1Qvd(} diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0003.png b/cinema/gba/window/zmc-window-mosaic/baseline_0003.png index 05f308fa48ae65851174f043c63cdd78aa789179..c5eb15ff12c3fced862416d8569dd50172fb0888 100644 GIT binary patch literal 8022 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 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0004.png b/cinema/gba/window/zmc-window-mosaic/baseline_0004.png index 6c8b94d45615f0642b8feba0b790c5fe37be5055..2fc7c052fd2cb6e12c406266bac4ec8bb1661f57 100644 GIT binary patch literal 7925 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 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0005.png b/cinema/gba/window/zmc-window-mosaic/baseline_0005.png index 044cf6ebe06e6bde2c0e3650bd8cfe3e1c7b35e9..906f6bfbef2423158ecb7de4900c233b93f23a40 100644 GIT binary patch literal 6207 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 D?*nTo literal 6117 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 literal 6073 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

j+TR$fxpB4a3rB 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 literal 5388 zcmZWtbyQSs_Z>t)8i^q#q>&J$hR#74I$v@S1V$J_a%hPGDF*~anIT3}Kthpj5QY*^ zx<$IX;0|okNe!U?pn`&?!9N9efBvq`nnqABupd#0DxTcv6|t{edp%Nx^wGh zZK&}T0|01$YN{z4BQTrkP#E)QMrSCo%{a%81bDexo8bf1b*thf4oQN}C>g6kIe1E{ zz`&0JZp!Ys7>=^aOhGc2jr_45LY6&4`N)x)gYaSNf?GZ0wOn|z!rx}1)h5v5e4c#S zi}%exEihZAIIw{kGiQ^$#qAT|{%7Zj(IBVq7)H%mmdx#o@0pyzTW+>1>bHoKACmXxLypfoqeSarGAc( zHk2syJRG6zjeq;4m9zILODI#q_@O{4sPKgCopB9 z*MA}^qm4Z(vXr?_(a`8EuF%7Syh^u|MZ-%?C;)4kzO%HwSb>vsXt!unYE#yiYi}So zv`HS})K-=XUlAfwURgee=FGjx5(N3YMTzhlc^Yc++i?1QbR*fGc^9>FE0fvgrLJ-R zwOc@|{;9>`X#KWdC_F%6P+MFgKc)VAXd`w;pPj0nTKhsG{~A1eJ|JMFcea)A%tu^z zj;H%(n~HqBxKDB)N>{rm+Um4UDi5#uNl^yWAFAUT2Fa9=Xj0&ogE=;x4V2QR04a4_ z9Vm-YExVT@hKP*-t*(5-pqI+=?gjJ!$0hf`(AnJl`jxNKZh>6|4$*k-o-s-1izSKDQi?m zN5%eBowHD8s-UTAZq_LZ2AqQ0Qj52ZqT=G>NC8x+PR^u)n6#_2=g;AuZXv#yKC2T? z1KFj>#(HYA++x;~r~o6akOPZ{;(H*oN4zU*?w~Q7jbTgo+0iI^-(fPqqtstxi2)4n zGbs}iKAurbV&VR=(N_0*#U#ns7J^ z*Ble`KVcl@B(IMy3CnYwe@Kn$G?74;tbSw6H`>DGR~iumUMPfiei&UnKF4FHA~_`t z{=D$ip|5h_Y+J=1^wL`4dVn=XEqm}~8rf|XFRx^HCljDC#A?CwfQsR^Ngt#<=)^`L zNTi8%(zpBGBQp$uDnSigO29c-FhSiTCg%BBa5Xg0x&hf3Xdh6O-$HF;7kCdV)l+v$ z7unI#yXNk3nRDbj6gWM8x8XfCy&KW5abE)|P+Vr=(-X6qZ&A$3M2+rs)6Kn28vV8F z^o2Tp^lJAF5R)p$M-+X1IQBU({Cfu5 zffG1$z<~m`?2DdI1-3;~&vU7la16)2w8X>xv6x8Rprg|XkG*n*5D@y%{)X%i;)QBQ z?I(W)`UBq8%%?O^kuBHN)eBpm)>-z$?>OH7t%SEeTk@C$I3+=Cxx%~8dISso}!J^id~Ti zEdrL=Cn2-4MZ|~c=@YXIXiS_4uY>s>`C%R{F+g`-|E?5X(s@lB=pbpMx(ZyndFSru zI{KL-aYHl!04bLP;9eWao1|(<6rFxdF_a8FTN@-MPAkymDzEjo^9k{_GVy{9O)HY{ zgF?r*F*WlC=a)BHc&!kkW!05F+}`^}z}dzorYKCw=N)W1;6N4sA|@@e6-xzJuur|z z-P}}bBKsA1I`(05`3~D>x>I6>9 zK)vJ15SJ~t0qu6>o=1w!isS^aD*W_qWWS6}dbX22*i#C9F>qe5KjB$czY5O+m zT37Dw)YH@z-=Sz8wSq<+NMZ17c^4>KNZ1T@#)c1g+V#w>XZnMX_#g?{neg}iS3OYS z!nP#5pdH@=eXXwk{#Q32)H+fmKl+=w)~$e5J~1mM4e`i)$C{-9$b!b~lOH@l+pmr*$yvE159mmhaRYipxWD-T; z0rl?%Sr5M1(`5AV^BF%D5;q`n=OG0UA8&=?8V;!h)(yORk+*eLTG3r;pIH!()OuBJ z=m1d;=MhAf3u|zOdRPBKc9cknJ^VS~$vB?|>cP=1fs=uB!mnPIeEog5e6?Z)?QJ{T z$!J2*pRhZ(eMoFk1-w8AmOt!vjX51G z!UvC{MO@75SCGv&n4z}8!2rQV8Ozx7=f5RuPMyaE8yV=AFf3-$aqb(fxut2R zFu#_u2#Eom{dy)pSD+sCqFrLtTnH_ZWaZ{xpNCZbWncb#h%`$?ay3=DwY1KFqlPb6 z!X?mYdczviq=x+VT&+Z4#*G9&`yyUdxHe`ReTW##>50gjO3^?^pZ)W+|V~%CX-6@4~+2`U9NA>DVU&{f? zP6^YeJA^&XXX3L874my|xL0r29>JUzxj$y=yi>n#GtiiHn)j@}wj`fxVs*!(eo@-H zpq4h9Z#5Y4dX|X`mORMmP|4LbVfA<*4-4&nW0q9h7p{#3ZRqqlTI<5HfVKN$&rj9- zJu>*gVYygr%$OlVn%z4(;CH686wJ2&`rRH~ibTIKDO{a5t19nlD_Tn2>s^r*{QG*3g}mGJe@_@DW-*5kO*toK z5#JDGMs`T9id_Oa`4Oz_!%#}vXN~IRi+cNdwMv4jrOnUc!F?!tZaN0{iO(M&nft0% zZ$xm$XA=xeMqd96<&e5R)DA*MWdQ?F7!QN}sn~ymlHPy{{_&7Id1cO+)qByTh30Kb zp9H;Z?_B0(=TANK3#bR!;%XRUXBjDvB#<*k8hA=h8~SqjWq0MC$qgWj7wroHzTkJinjV%}A-zm&O{pcUdfy$lfmmFSC!Lw~pjRGnq++(_D+Wjm$M%LK2;19e`%LGj*{z+%vY>vBZE^EbO zqhP+Qzy0q@!(LTH2;c*g(8Qw0oDMHsk~1#6MBX+TI;NhKi}ARQmCYbSUkpT)n9Rwb ztu&X$pJvfpg0;2GZIkI8nzD8rY3W!=5H4?S13hy0NKVMeN^iYD|8Q+pbKOmS{^$8PmP{ zX~e`6PalsELE;EaDP(URk9&vrEL7TarnMwBHnXFbR7{ICf~;|lyRYBpLkPi_F@Ckn zBizp-kNdN~3PBzaYI6`tja0-@%G|eE*fBjmTFYFyr*WApwaC8b92?HVs71Y2nm@Al zq3|rdTA+et?UGh3yTv(~(T9RXqT;jme!G%2Me7Y)|IqZ`RB*+RrtGw)uB8yW#=IZ) z5mywM4p!AaBG@)_WNxhmrWvSMxLZ46)G}BcAG^(YNN*6&}R!Cc{6| zyUSMxv85kU#74bXl6oYKZ}RQ(3?gmof8?h#p3%w|Wh$Kep9bhZN2~V@9LJ@K;3>5{ zcoi~nselgJ{)x$2o5H6(?`vtcVbv$n+sUK(;UyO=bgAO;N&2Ld!#8Zo{zO@P;SG6H z!@5j)Lri>ne>7pdXOfC2n*Zs#tb@0!b8v!AcQB$rsJY)V{_aKk)np~f|45Ux7)Uak z`$wtKab`(bzM??P5#xy?J^_U)G^m>g;!+^srp<0f*HlxJG)E-`EKj7dp8SbiB8UxU z)u!5576T6iivm17{bNM!%G(DU%FQs-5gIqK^7y`T7$(Tz-21 zFE|urVKWc-cb6qZi}@sx^J)L1PJ>IYb-%2JJo{v7006)`yX+2c1`%a>J8|#s@BtH3 z`RT~C4{7ZA8XfrfhH*S(Q4gP%ecyjFk+;bds`PT2_v=4i=|fe-qr3m5?qnRB z0nFp10`|!7J3*c+ESf}*SO_e0aYcysuF+2^_a;$iwAZsK*R({<@iT>c@kXgcuuOeD zUsh1x9hcdKs8WvOG+;K5Uq_*+n>haeY$4;gS~-Ky16QLY>~WH`jE5F{n~1$YnN&nU zGSU0c0*?~hC!JWgp~txHfv^2EMex0f(dL=hso+dn-Ez%3)rCq>djujdQbOK8jJ54I zlUhC!3N}?<@04_R%;#|LrsM|J+BjqXk^eU(g{4;Eq)l1zmeQhK0xE;E^GWA}9?ZH1 z>+T?Dg~V(CxMEf9ZmCw=`yAGwqZ;z*<6i%YI)WVZ zH?mA%`eJ=GVrZ+gjqm>X3aD^AQA#3`t4HK&J=((Q8y-Y-9wtoB=n6YD2p>oc3}h4S zQoMXQL!VGxUnX*63Zque<|tna_B1pAJPt>U1>PGQeq8ur2lw#X{&3jqh>#E!D`QLJ zQKlhJI{zh7!*;btHuqzw@6QZjZgd{1DmH=g-z2YHp!nHGD(ZeVexWxr*@t@A5~kmP z1H4yhWijmHGB$eDnLe!6ycsW~3@5(jmd|4<3|%k3SnV7xTyz<%jHr48>x?t;)OB+u z_y5}I>+Q-jdxE6m9%6gahloxh8r%g!8 z+{Dh<5j;K=;F!Fwgs-$v`=~Odw0;N{Vp2e<^G*X1cA5VXl-A`^Dh(#AAJm=+WdzuQ zLagg5Os{yi$&LF;Z_EIUWIbD1r|(TEj~%3prAZ<1+_!nzg{$dx_F;-z5?5+u zkRFpeUbS23Hy({xT)NzbhS9H!AN26OCDo@*-+!II6+3?3@euv)l7z8XWI(M`CR=k` z?mSAzvCJrarIXTgnO|UoF4zqEDO*I+JBe91ZmXg)YU;00MX{8rr#f}_tRMV(pN?@@ zK%7;WC#fJNtw+unE<4#Vg&!ONJ5a(GpJikpSx#h`NF$t5x*Czz}F;XTY9+UX+# z08j>MC@a47%Reem1X?WSjeeF*U^r#xF~gom2pi+P-+;kw*`q4SbLRnq0L9ZZH8jQbJbeFvX=paFRntvf(bFb|1~?7Q?e~$G;cVQT!qbr1<{4fbAimqf=V=+~A|8=Y{4yBcdtzZK1h zfAIS3d*vaQXlf!Ds0VA{|{E*|)j&H5fJZePASdRovidv0D+o__E_ukOyp`}a_tgB963 zAF0K;Mz7P?3}mOHW~egB?Jjbiq9vN&$cLQ~Ivlc9Yh`KfbwM3;as0yT_*dkG z(HJs5AP-@k17H3(^hVj_-E02$mx2^$YHZufE~0|yJ52ual0o7y_21h^AF1-if~gVZ z+7H0Q*zueLxSAg#fq$B1dEJ*9VBly`@Yg+ z>;8PBrX+VZ;;7o)5%lCf095=eeSLXpTx`D_1t4o4rAEzvM>qM?)pDHrZIxQMsj|tJP9@#WHH6!w*2t3_5xLUb1cr^h?$-!B4I1=gXVMrVn z3syTGIe6|dZ6O+w@D+g{9SpFpgJe^G!xx2HBYU#O9f|gyvrrE6v8ZJ%n z>N28owSb&x_pmz0UB0HyPEe&ZLm`f#IJ9ZY`wE_oj=quwC4w9E01-$~#$74G%&wfHZWuEb&7jyWU+r9mcOM zdEbae3v6CR(sBVx7t=79I1HU?xz3*eX$}o#3npJn9)p-Sbm!FQSW!lvbEN!kbEot% zBesx4$%^^}g4c*bF{f=df1{=s=#VnoVoI-*z(a{cyHQ`nHrS%uPOb%eM3p5sh@d_Y z`1#I@6$^EF(>VR8wy(AC+*Ot?mi<@0?j`PVn-59~cO^nbX0<a%ONf_Lod7=SpII-KIv(rbY|| zl3;aQ@rLh~B1vVdC zSJ?~zD)noarBf;O@VIl4waXUT$x+}r%Z4xm40xFUG2@g3zoyJ1D=GCP-gR^sKM0Ma zEuX}F7mvWCp#8{CQLXOrr@3xVuuU9xaa7r?BSG!2*_~@)1{q~ztdzW8=)63{yhz04 z5>i<<m$mbJV4Ym?I-UZ9L;hh64w38aOw0(F_<;_<#37d$gBClXtV5Wp%pCL#OX zFDKH_<7UdU-mISZ0E0^IjKUfLYDc?5*O7)|E6R2gs(e7Pj;Au)_7p-3Gh8P3ithFx zrXpLY6r1qiFKZ{$R5XmN>B0}pl1*!Eh;eT`nzMzYoP5V?kNX z>shS$2swXFTp3>?Q7iLf(*Q}(9!WOh%#K>+(aFR^15L#8yVkg4@Z&q+KFYS zZ;4CKS<}~1PYVAsNfz*U0UPjVX^HchuaOG#SX5M)tlOwM>MH0Ypfptw3s5@i062Lf zaAa&jH(!I;n;&RIEt<(O4um`Aoeul_tRL(|40k|wmvgvCaEzWr55gm9->{QC!nrXs z`N~qSD$))WnpK{l|3o`MS`iuBR$Kl~s;|gt%vJU&KA?yVGNmJJ|xxy3wr+%UoX>?1xPgd;tY?BjC`;fJZnK*d(awym%@1#XH%?Z|$E{)~XX zpj#9Vtg`7v^s1Sp9r3!<$}?iBO2R&u&{XBsglFZt6IpsrsE3kDr-j$Rs^(IRfb=R% z{F055jgK_)T!CO(X(J~ich_AeMnXL*O+nc@NR#MXt>%whzKrit-GiJiXSebqw4Kq8 zD?(=6Sbg|<>!kj!SoGVBk`j>elL1`-7QV-B&ad_$VBb@IesjwKAP;j9*ftv@(9?yo}ToV?} z-BzNDy342WefoJ`w^XDAV!XhAxQaRMjAH?X6h1alYUjjhpn-Cts{c;+l2oKel*o+^G9s7{gDstn|)V-@Xt|QVt6`zPqIo3;b+4+~Z`4^ty z6c>ACy$m8E-Nq-u;WVb=X(MU|bI34CZimq}`bHO5wBbf+clD3gBkh~#_xf0hgJZue z;eBjX84JADZMgvL1ZZh9F(fEp2R{4M5U1P=3Kl%jxoGg1!mlblSTzl))`5^eiRUm|*c}L$NQ9TIUC!wVV&3{xLDJskyT8AAT%1XPBLaYnu$YV5 zZ*Jt;!W7B@k90C*X1=FvDFrOTmW_yH&T-OSsv|McnX*T9qac( z8mN2Nry{SFm-bIDFL{OSl%&nu&;| zX|!yLqi+ia5wghxntJ2hVt(v6&Xlw-gF;_4BIayYpXt&Pu|@h z=;bW$I!Muw+iZQLVPo|)a%MXxCm=;LevJDS?y?}ka?Hrqy4pku`rBQB4>=9qUpvo- zEZou!n6g&9z;g*Grs<+6B%MVh&c4@Tw7XyhgN#MDH0TQOO3qIAY~xJBtgu&>(V3E5 zoSQ~dP7f!2rXk|f(BGA)-qchjg=0Aa?+trcsc2evbV!ExlL1xHF$ZRp2B!cx&(J}5 zpZ9Tc884lx;1}x%v}Zi^p(5%p@xxaQ5<7@!3JYHGlF4Y}zS*WOF=Ze={k6 z>RLeAF4xs;90mucr$uDzR2qaSnHPvljgTtX_`+2u(U z-Bv!>sX%k{h&($J$aZV`#OS1R`}if{k~2E}=U6X0R7g6~+-z0Enu1KdX1~H4wC0|f zKQI;KU(%*vm?RkP+L{)pDk9ZJf$oTg7~vFDRLORIg0tC)&A1BNMTdc-e6se7X#5EN zmI~81rc()J){3H8wlSt*0yw#R4giw29RI9U%HyAjH!lo+&2MvYiZw7U^V|o8pEeZu zqDpe;hLZ+~tI+&P4q_h*S-}mNM&1u``aT6oq%Er&df3O>NF=W1_J)qv@mJ#Hhb1*X z{(MV**pUDB9N~utrdjFC_&YvnK1JUGT`jdCE)|2fXFp7An=Tmx&XF+nx7dT;I2ZrB zs}e-Yx9DqPK(Qt<`10(juv$an<@%nSSdC%056n1KPj^}Az{I7yNy!`me-<8uWlQA| ze}yGi%*4{Q8C2H}%SD48QN(J>MtXhW!6SLk1N)P`+5e!dC<*(6JPCo~o4tBQijIGu zu%;|QVlfLEVO{YfQ||lCXq(3TU8(5~#AS-oO(cp+a2B$7b)ssnCi+p_DS9%o$}fEAvZCaqBpt~kKXo#I&b2>b z76Te53y=HxT;=1z0@-TmsMbe-Da#1mh}z@0+arswN<{x9FiM-(4qDn)m18vujIDn6 z0TPpcY#l{nZ8Hz;sP!ivpDyLXImt@tszO zyYix;?;BSBYo_H~invu$-~A~z>x?%@cNKKjDnf1iK)S?J9t?7I49tSXbXEA!@V z)dtS6$0-Xd^6eKO=Z$T370+?!ahOp`uZkIjPfhQ6*&$pgp^FxSNuHm=BvS#?yx>~A zw$YTZ))4!?0_h(@C$W%(>-Hxe&7efb@31n~@^Tj3{i?~2l;fuKZCo_}f0WD%;P4!8Y7ur@J2xDSfJV>#yU@`z7(Y|HPqb zBVBewZ}NtaQb9DsKiwaWnOt!!?W??yhz;r)^w2GW1Om(3kTJ1-kW2K#iQP5K_gY59W?}U-R+49hQ}Yn zzU7YvHdCHqxe;O4>HCu$<5Ujk2gl`sq^44%XCf4=KQOxyE_}``h;lV-di0`|vpL|u z-O9VSuWtvPB(aJ;t&(c@V06uk4T&^E5<7T^|u^V$ieo5a|^jWt~&`Dut}0OgZ*T%0G}&&-%w_oI_?4F(kP<*B|qkd z2;YHy53Z~p!o%o~WU;Y%kiJzw*e|3@3l^;6NJ-fLbmon%m+x$-a;Pj--!Y1F~p&jdPo)r{uboELJSR!QT}ohcoUvzaHKPk%Gej2yXM$@zL-HP>-(WW@#oOrZ$v+AMlz@w`n6 zpBkZEQY*5%ediEM?gK-H>AsjFwf7g0Y}P= zZS2^5KKM&B;QdP5gr$I^sO|;0iRh)RCLR{bQ5LLbH-onZfs{HrK{AnP%w6^IW6y%) z$Lk-l{sMlG{Zlm{@U+tvk@NM~x>6JtDe`BOwvmm7ahT=}F>HB13 z)^T{sC$at(#${IH)DC_F#cTX-A2M|8^T&^&O2Tqsl;)PybX@XFZ8LZ$CIsq|U1^oFV%d#%zD2GpxQ)-}Y-TjGAP>Gt z4cQ))eHVDgvzGHPC@fn|oijm&F&=p8n>aeqKHmsmCh;x;zHbqoWprEj;{wn}CX`)% ZP|oBKHb$93Fn?nK8Y;TVb9>4{pEQdsw$)YvUUs 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 KDOV`I{O~`TqlISx diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0009.png b/cinema/gba/window/zmc-window-mosaic/baseline_0009.png index 35d54bf965786659ce85d3174abf8ff63d580c75..e2d9b13373a5b6505c3c3b79a6d3541bbb99b1f4 100644 GIT binary patch literal 5032 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 literal 4811 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 wwkMg(ud)zHnvTrT7`qm-5T;)h6tJ05=|DY5)KL diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0010.png b/cinema/gba/window/zmc-window-mosaic/baseline_0010.png index 8b555e49e340a6470ac8e70e446456b4a3e70ad7..dd985ca721dc5101dfac8af47fa2e4b56ab7587b 100644 GIT binary patch literal 5062 zcmZ8lXH-*L+CFqdFw%=im8KXG2%>;U?^OZm9YPmEiF5%)ic|$est8C4NFrT&2@okt z2kAu#9YPBMCNp>Dely=%r>u2;ynF9w@AoMuMqgKrj)sE<0024-b!9{1dxv=EQIis% zP4)ib0KoW0Ls{VoJZm#s0m^S0h;F%JW%@y#U2DpzR6M>~WrL(LI$V3AhGU9WGZBQ9 z+kobE1XB4VdF~;OY@ia+-IUhB(4>qw+DpEiHN2WvWFEksb(TmPgRU^p&MbYA65bnn zn`Sksd&5a{s$(nPy&e}LFb=DCz^rUXg9-;LbNWhKa%WG+k7gTpQ}f8P2@^t*%nv~c zaDpEOb?6p~5zi^{#)vl+jJoElqh9cbCW2ICmxrIxRKJ>QGM;)wAK0?|L-3(n7)T|S zIe0Dz9ZkOu`fSCnOke$inK_f%d~DxjF}+Ld40Jiun^_dvZS*CN-J~tT$)c;((HbZ! zTJ%11j8yy6JWtVllqZp^Dl8q($89xly}*={SFP23TXN}UL(Nc(-Y{O_++Yj`_Yjeh z3-BQ*1nSC%rdeYB-A$d(4GyhC?;^M_ro+lDmv>#b8DM^Ows;$XT^A1IzFOt*43A$&i6S6go@Lp-Vc%tJMVVM zbhYrF`aN~;76X2wj~G&xnMGTSZl*h~JwoJYj0r|6=2DHNA(O0HTh#CP#>r#A*(Jf} zKPDI521AF}>qKkG!* z&ZS0(@>1sqcR36Io<=10*7kc`zQ(>!rbocfW{#1V#fHo-OzkKwAhhZ2O!#zt^ND%o zk~TNe=qA~t2lruSG9L3^Sos+PjxWY^dw7bbQXsb>b$VRD$nqd zx$jF6WQeDyi=Bd%MVgbf$;wR{?XBh$V{t~ znR~ssj&~a$@78@ua^dFBCsuEGdk_g#E-|h?Ihj1oC44Ke`X~%b@0eK6&XGBE8@?>$ zN*TT(3g^c!VEg*C#Y@(MoWLP`I4}Y-JP2C@}c`}?SR z3*9gg4I0;$168whzv|LUQ~bYo@*9VYQ98;N?qNA zOWnKY2dRaGxJlbJO7Xv^QnKnQdsppaN)}!42nxWp2m)M5L-IDzg&YP=s0G@9jgnkNCEwrCb&m`rvT364zu%sFOgz z!j?bVOUTPN8r{@NsXZeD%nQXi^!+>9Yh*aC`{VnDVJ-#ZwvsapxkU#gcIO6Z1GKC@ z_XQ~|60|mztUqB4cqbS5P|X_XpI7c2YJ#|nR+hZNjEq=WQ9u$#9Z1SVlu~gXi~Nwd?@#k{YaY+>EF||V+8r)* zNCHH>^AN=HF`vFRg;JwEN_zrD5e*k>o+A4;)PQfCy&zYd&M$D8tLg2FKkFW1!9nOm z0V}VC%(bQ+kR5ua8PjIDy#7e^Zb5B-9>N5bSsqx8tjQW@VG$1_MBi=G^o0a5SI*9< z7ja|xzd@B_bbhf^s*%@hW(?~)P$UF#tdP+GH0#j-kA~HmNt&$4CLd&d7g>sply3_p zw^fi>m7Tc_ArumOs$x~yFqZJMtj|nV3dggcQ)u|(H4-NAEKopCvk}B#Rk~{Dtrd3l zb8(E>82=TaV+l)*PtFWL9s+Hzg-it=?PZV52PTnQg6J2jS;hDPZ*|;Zlz;1U5apE<^RD39g}$4ILfFq zkTtT8Z%uY{QjoA@4}k$cCJ2P`*&DTLUV%<3H#n{lKaG{aE{!e?p+s0D?O)C|S0v3l zj=~;@LRTeK=QuZh(Ps2{;`-SiT+$u2kI_+}Qd+(rWI5lc1~gz|aub?kA{AU}UYjd| zlLpqIQr`WzKw%%Y7S{JP)H-`JqNiWB4NH=a7~lc)1F#m~Hul_*ETf3PW^|n~Rk+D) zYVWi&0d4YDJ#-uN%ZQ3Mosj(jdbcW z5?=X%tvm^1sDE_ya>@`&kvHYu$3?~b9;R6Lx3P76?M%B*H)b5^mA@+RKlEYIaWA1i zi|VoaLd3Nqf}+2UhJ`4{Plw{mY!oCa+E)v_c=bA1B%i;y;h6DA;@1 zY;!DWMfgg>pf$S~^Zm~LAL)cH%XQ?ah|6le8N z!+1X9mE-(-OV-IhD#Dp<8O2XJzz%G8S`HF#<}4@HI`5@q^Ft7@qnlpygJ@?XP$FxY zz~vWVdYtjoz7)Z!kllV(-l6U4WUF5R^x8-r_nAAM`GZ2N8R@;$s=egY)8Na%Gd>eJH!x;HfWfyaKXYC6zXuYM1kkphmk`i7B~V|2KC@ zpp_f`JFm$rLA54}r5!Jd_LpJohR+3%(}I$jrLsQGc0x`yu7GO)wU5(n-1D=r+;*ZP z`y2X(5ZqBAe$?_=O(9|MUye0%e~2eg$6n-LEV{}?jvBry5%GG->1o6 z8r}q(OmEGAy;4|cJl8sSFwir9OZI1a(fXghA!GPd=qPx=?m;+D%Ex!+S*E}yOBeX5wwmnA2BdIO zevkYan%be?;n&vABmZ@paVPVt4TEfT=~)W-k?mtS^5)K` zb{eJ{@8sjFG{GB9I@mVH=DC|0mPKR})5&Rz+{<#tVDfk1c#H*uDAd_T=jf6s6s>RW zmv&It@$hSh#h0S8U5Do%e7iBydDOVInHM?=CbD%OxL3P-!(EV6nJhfqDWtV7ho~kB zhKf7)y6xamTLKqzRlHeU^yh!THw!{{t<$_z1DH0iBjfq|H7Qi29wEz}X-b6`j~1Ac z0wXt8uxkxu<%E0A8U09+rLm*Kr0i~E_6{%nwz>3NI))!&||iAnHW<4w&4QS+Q=` zdy#qyK!$ly#r>`Oh+s;wLcWBx=n;GVbo#e-Q0J>>NxEa*r-PX%Tq_@|gZQbQ$>Eyy zQi3xuDmR3p%!w(b?gqEn!F(EIq+Xo5bxZblqG8yEVE^9A~N!0(eH*F#;E+UrS zGrK`Kvo3~AEH2J~)AKdC2C{UqewARPI%5t4Qk5xIDlc>|1!#vFluew7?aJw9Gwz$6 zu>gt=P4rk7>FONHu9E9U!oRUfWpp`eJvde=ihe^UJflp=p9gw7PI%_@;~`d!Hyf!? zA$zk7pWhA#Q_a2LH`>?Gk`pkj+rJ(s3l5x#1s`s<0 z^3aGp{fmT59peWxwarf0pp0dgi9N{;gB$kmLiYT`4!CC5(~!GC-G-OiZD4do>>Q07Fm+(_KGxv3W$_$Zpx~XF>Tl=Ksl;cjtHKmt&wAcv`+oEbw1dMpcJ$lxH}Rldk?x%#)MG z^Hbb-&T(kW;~hJ@c`2yg^U`13`u{|;Zh?QRQ+}>$mZs}B zx)CmkJr>2j31+Jrxa2Zr#@>@-KO~cz{}g@yCW7U>5z0d6s5q-Yj}?TN!*3^leBc@q zL#lneS*6bPHd~^jK};_9TsW6it6nw%>^wJ(q$rr z_s*e=DxZrL21$_#;@KUF&gJoA0l*^&Q&v_(T;FHw`NE*=KBc4pZ%+-K*7y04I5-E+ z3#3G%Vg=z77T@Y9zE)36$t(cJo6RU$2T>H= z^`Ki1MK)^^w-d8^ioOK={EUv)TeLRr$9(u%eEX|A+XuN&0tFr+84OZ+NGqqB?6e-y zyu_C6T2Jfbs!H_A-2z^AR%GFXh@&-6jd7@Lr9(XHp=Q7*df*u8YSny=EF~)Ubi5ZH zToy4t&&i82bA@p3QXm3~pkoLWg)p~HvvM*eJ3cPHpyQ|;G3UNi??|6&AbXcqu$ME} zgpP0$711GV$bF+(pYc?zE5yQyPrdK$4o#u&fI(Gg1?|;pi-(r|o7epr_RU;beAv~$ zyS+3$Ueei4oP|oBldY`g_Ic@wfSmSY@Lyj#Rd7y@j#?;3;7DV=1~0IDcZl*Ot?55| z>}o!$PsIW3U7od|r1I$0vE|P6PQXR%79&;_jod(ciyg^|Mq2%(z*{Njsbc@Qxnx!C zC8EiZkh2XsH+j3xi?kzbl-B1?Y+HW5+ArDjgJAv z@!!86!-u70nLP8};0-=}o7Oh2lugx;h!9=Mq7sR!6l3aatO!2DX))E`vaNN<4K9^o zwg6GPYwj-z&h8e0mqP3#BadMfCCA%SUN$nV%Yp8j3vSBkdu;H7^oC5As^e$MLBBu8 z8)SFa9aSF|FCAZZS*Ggi_vgH*&bH`f!5c?Scx3U0O#b4I`IFdq8GRck^2)o`c)x7I0toqf*U-`?N;)`@?rt8s&xjT!&|+<2_1Zg9D8UCxi! zuUxK;^}Z4S0OQxk>MDl*IU8_2N51E|-C>P;-=s^?m5WXWH9R9g7fO!UYw<6?4*$V~ z=H#CA&J-SK7k;g10s}YgbI`hZIl69j#!|#v*o{V8iQ_A>dQ0hYlbqzNk>2la`y|6E z5hHX6sl6jylDfelhcEq0*y2oiVW1ey^>A&5lKY8LbD&5`Q(!2`y!miC-LF6-XMxnl z=FdPkrzvc3sNmuuGjA#=1zIvK@?>F-W1ZD_qMyCjZ zTs$bjA`?eVhe`CrY3`>X4fNn26vji=yi2AyNIV4CZV_rSkZ4%(kridFB5BYXVP81E z3Wfme2_3|fHx$VO?LWPPNln(Ex2?;6Bn0~>g@K<&;nfB@ue~t&6GwFCX53x%?A&jCa#GE~Y)=pQ zwPY3h0;!7Db0yoD7h_^rv=A zl0t!lqBPeCM`>+W0O(bQ)=L|)(4l9w#JTO!4oW3?eO%fM*9e|icbliB=W(N27RYBY zNLnTxHGH>SO7E4eMA=nk8>c^A*X#BWk9PbSY$3g^{5s&Zf`3{DAVmOcKP+#24R$3m*JsQX<0+Jg46A2C&u21 zVB$XeVdU1B6vPXqY2521!HKA_KmtUdEC&-^W;n7Dsm@CU=&5V6k`_tdPiel$W?Iyw znbIHF;M%2PU@4-f_c~tuByf6PQhM=>>lnWwoi=34!l42&TLAiilqdl%E-t;jiva1r zY$Lr6GpKhrTI=hv-ic9GsL}}o41FrJ(YV^)0Z9rz*=5>i1lKokY&1^Cf8c9=DC1XZ z4-nRlWUQr!!7?PCJ;O9fO4h$X_&I+0(3_b58D+=@$cPyBu{n1)W7XN85SKBgzqItG zGyk`N(W{}*iMQx`Yi50nXpR0^HNO`9YCUxe@)NuEyu;fDxMM{y6fwNFdw`SKR-=ZF zjEuNS%V#x#m=7(@<&PVFCzP4Qyu1YjBo>|QeogPQC%c{AU&tt9WaI>U6+|YuXY zn6sc;%3M?1UR>N+b?yoM@Z=L|s#kFWg@FZNo`D${1c}xq_1M4)lPaLrNZ0D?d{I9< zHJ~&j!@&Uw>n-usDs>T()Bjo&-$2-m@I6c>yAAUQORp@BRdNeiCeG;UK*NaXSDnlV zL`{4eR2V1;l5%@i*0zs0x*Rb!WZ`9^2ia{YrEWT5!SY{RvdWzLt8YzgZWX?uc`FUi zHa8V6z03Trhz#I$JosC6B@|&fQo8h<#T*|46*kquk4sw~d|L3jxh^;sdhCjn3mE!< zsh12Mt^5FoTa~jF7t1OEDy*aj!mF6LFjI5OTZQEc57Jx?<;Hbt_Z}(4v=o$+LFg&n0?a`j9E7q5R7^NM zZ~hQaQwL(s%M;Hmq5|OY)k#@eTGG80!OvX|9~i9suENHE2UEJrsKhrRPvlLTUG4u?Cl$(XQVqSqny1A`N9YUu2UN<8-!=eZ%pT zq!_@z9Y{maCV>m-kJiRND3REne)=7eu6wIve|R zZhB}hZjSL`?JV>J+!F|VS0;S5WqfpE3vy#BJ;;;;_5lDxqsp+wp7vFsK{cn0HlFK@ zYJ~4PO&0XmPvaT}l-SKBH)$kt=tw95WtxUMZ*UUG?h_=QEY-Z;Ojjs%pVARE>`VXt z_4n-oU8em~4G!Hc2f5V#mxBQ7FSo=TzbFZlK zqKZ{819u&};PUO;uDG{;MbRrjVC z=u2`YLtnvgYzQi2x(IqP_Xu&RaT(7=ltt2bF#8PNHj&@#RQdM%wq@1m%&YeTC zxguINy}?09__VBh#p`o1qib(pguNW2UeP7RoLEfjYQ(5BCK*IKOS_=%bL$ng?BuqN zqjDDvm+Ep!2UqLufl~OC6xiuI?Y@5937sm*z-md+`JSO~y&+pxaIMd|;ki1MA6doM z4_?KSix=l^R($;6o`{{C!9JW7X-I(~m-)$!l7&FmI63)oV{2C~g2+EM zGmR+2S-x*CY~V+j5CMD<-qfrV42JS@_8ZEcecaL9A^+~G5!d=OuhC?sP{ULZvmE5y z4zF8us}wCL`7)zuZY9pH)5*Pvw)czD6r)mf@qD-NHnVQPI+z2TYb;vF+a|khayIpn zK^?8aXX6FbaUBylVda0*>P7Q^Hi1%nH}5(=68Wo@9|iB-TW~sx5EOq`NXz256k|Rd znlP~Tz^`)m53UX-p#yDfp#FQ^DjxhjHoFIX8B2{xhRj?0>nWxW6|s;jy5G2@}3%Hm#naeL<_+wS%-8B_U>~)xtY#d47&vd`7!9Gw? zzbYKeZ7TGPof&HRE%W!XUdV1=?MCEycuTQrb4T_pc02ElFeCf2pW>?^CI7?7GP1#j zVaFBc%FARiXnj@@7*W)?VcI3ttQadAFlDVmnHcweLaVAYGWr_lt$BkYhsx5(h|_`A zS-n0Lc)u(MW&lI5VPcO{_ULNnEwCgLZ4?g z$wnoQ%6Ru>OB?hZPtT;R_n8~2YPY@*NOST(BH~y_ML02a=A)tRN zPaK8JAS|(@7AYZ&hFBdc^v$ICosj318L~c^g=?5y5FJ38Qs`LTSE-wogoJ!eR!VEx z1aDQ$fv(jaNc#iSu&bpjlIC=ww}N^Uf5b2OxL;nmd|1L)Ye?@zU;VjzYM|!_?vdU- z-m9%MN$B^Q+@wk`u}mKui=~Lmw7TkYlG+kMM_X;=U<#4No;bEt6o~_YK6;)y2g>*> znO2@JrJ^;K6JFDNI)MT;)(gx(CmtWku$>O&W)+qUTGx&0=+y$Q6onhq@Z6+8ih;jXBucAMr?IVSv zBw#do5%zZ^VN6OP%W}9O2Qg6vVY|O;t22Fh>pu0AV?eSSIFR#!gfq|-7I`AXJD^|P z`ve&@P0$XZhbi2E`Ifj2^M2rV}dqJ^0y!iVY3x zmK)Q2Q!^spFR0C`)V%F~B^Ojcdomt^Ef35ECe2zZu)PWf?mq%02K|bXIy@_UcH~VP z_)F4oRTiE<+!n^tmyh5_>o6nS4XUFFjHpb$X_&Aw@4Q2eNm!obe$w)|xK99W|DrX2 z>_4sbpNCtFV1k;)VtGk#rSzi$+4k-N6g)q9n=B`$r^s;;x2J|Lvu__OVKB&)E3t)%9KC4gOd^o#K8(_lY#K}B3K$vr4zWrx-3bY;1>T*ji8 zTw4B-T?|9*fcAaIuc1F-0MD*r4c0D|;eC&BE6}*yHe?4>RhJ0rELMB=b9$ zSXx}Fgb`Nt4&2)im3QZAhg9OGv#;;yW1QKTlkgIcqDwk*PLJ?D4~up@(>SSVz9ru9 zp0f5>eR9z{0#SjrNGy4mt+xGk3!kQC)=x0ETN4y`7Oqr>hk%Sf;yPW95*ZncUAAGY z$yV87?0fd(cYfzw=Q`)Sf4=YaEZ6-!_w(M*;Pp1abRjUsSYPK}MBZw?jw$2^&$q@e z*v*gOvW8ZgX`FK6Le*6g9DMlCHdbAvlu!W|KW4Y!$qMG%=!Tm`&z++Z9T3=>52&; z=ORvRJuG?ul!hL`j`v&Hht@Y^?{wc4Qjw8*9EM*HyNBXGyuzv=z%JygD|(xhV404l z!|>~{>4uEVZ7M;$p(CI6t1Ok%3It2a^k`}_#t`5}o!_67z=)?czKm!%x`=DxN5|fH z)Wqu4Zgtne#XyMUcKeOxeJ!$Qbo9~m&Q4CC@(C)LzMW&Iz5BR4TcFnZjHQ8nqEs=B zV3+Yu{^8skiP1*8B|Q?CM4!5H1R{7!w*Wo0dx!N#REOW@fRkFilC;%bhC$gJ}w@s!R+dMNN!os2k(}AwI2upXT2I6y4!f zM10_|BCqqbv+Gc9$m2Bfz0P8PtD>#MDAcR(-|}HWrWT3zPoiOWTnXPdygr`StuQtPMK-Ps(*;@ZZq+>Mg^_bO&oCwX zC2=4^Z|`dWqTL-}d9F`+ik_=n_9_j=9Q5mU8Mo2#nIBkV!_@NK`SdWIGc%J#>Z&n0 z7BDE|Lov!I0_T*qE6uOKpjO`2JiR#3S$XvZ z->sZaIUfXJKas-5*+Q$%rXsGtu#J954_Hj#Rv3-$woB@wK@tEN% z_ym(vv7vcmnq%JdDZR_-l>* za@fYBCOrQmTm35e<4~};FOSc;7=qO!sVa5uHgMoY^8iW8@ z8h|+)xn{(bV^fsT#YJX*SuF?@+vMgKN$^EQxw))A_La6AliGWltQMB1a-v+Z5qSxJ z57bqFlRt6Ab>zY|+vdllx9RbWKiq>tLd-MCTb3_p$Yg3jXhsT1mb7_IQ2!`%88$z3 z4)MjOzGG57d++CrHG?24^}Zt;{k^82(>UXThLXHv5Q%SE&xc6EdWZut0BNkq7SodO ztp<C$7LBA@r3o%CwUAc$ zXg|>&>i+i34dvpLNA*NjWz@e#c&e*6>2qIcN8-r@w!OD@`h>y0Zau zM~p8fz^cW|&D=t-3p;91p7ZaofVb9HM7k?B%JU>IBxy`oHJy7hTo9QMyB*j?ssA>E z7ARhNwAhxv(S44)S)21`yIu%wYMn)_>+G+^n@^QJl|R}?)6<@FC6nU8@PH{pzOz!A zoxmgSjwhe(O!zjbDS`CKsJ!Glm9!T*vaSJQG{7_cxNTyx@okd61jza6D8$NqQ zMMQs3q`Z)`26L^nN1%3P6gMg4`&MlF)&u!k@$xo%HMzg>kMhLd-5Vy(eN<+78gJ|T zoh>h46Z^;J8&$p4I0CdA_*KA6w>q`0&A!zil16y4Hit>ye>Be$X}q>Zeg)-vpep$H zweHKcYnOOjT_L^Nv6Jae&2kd;|1{MVjZEqalrGoqk^$3{e{>`j2B=2~LA-H)Y%KhH zaim4pk&1X&i~7#S)&`@8WeWpVgkKnedbh!&Qx(tj*$^7WegI~ZCf~6tC0=qK&~2uU zC`3-DaPsD3H!R}6lUmk!)gYy3Nni}4a&GWdn9_#99mC)+#drIkmL9O&j3Yg08R;1p z^}6=@(QYNd%c{|ynGW^qt@>ri5vbtx7V)=$HR&J54zKdAECwc?J3AgyG-`&U zX-t>qsfkdfTK1bLy`+VC)rwxf(}0!1MQq-$=4EYaK=p6vyTw0UiA_4IX+-a}x>@S{ zp=*la2aH;fY;{$@EOZ*1#mK}BI$izJ@IFSRxM683=TT9jx->HpBd?0Mau#71`&tjS zI=+h&c5R6AV~AakZtdXTUgAw3DiD0dB%t@^eY4V#Di})oeF54)q4<|Qj5pqThF~0tb=EVC~&3(^rn^;vdKS=%qUI^r*hu=mi zG1HB+3pK~i4(nmOe>Z4Gh_iiymwG^*gvi6G?wt9?NkQBY|7Xay!MZp54{nP+r*|C2 zCwYql+(9*l+GCN$)#H>8F?C6(KUxVF%xEvt;qN3Ss^=!>szClNg6jT3{UN~r^<*nW zQ}y_C?<39KX18ImVq4|&Qg0NcTG(g^-Z=b(8xRi`xM+n~C8i(rsL?n~!iLKXkk@6O z&DO^0hsZJj)ue<6PUL{P8<*Dc>VvDUJ3A({C0~c**7Sx;lRD6v8qs^UP#ugE&HqTS zFj5j>?aGCx$dTzXc^W_|;D9#kj`i3}XO9g~YJV;qGVfY&=tS*1(+1ZF=~Ek6gUYZd zQ14f?Zi~z9Jnnm=Os)Lf#X}|`^+Wsb#&wbNY#K~DE?d%@(Nfdl9bF3cFS2lZPCPT9 z<>3PyePVh3&{UH|(1!T4f9XD*I3*gz!I+K_Qa~HGjl5sqtW{CsPZ0Zdxop~S0fd!k zNr#MzQDhdtU+f*z;1XJxaIvk)5V1&~pA-PPX(Bj(g~-pzW8Mt^8>A-=-HhGo7ZIX* zsvL@sUokLkm06gl;BN@rAxx}_17oHythFSH;kc*4(@v?mF+BV_j#Pel3;u~dx!Xf0 z0NuDdlglY1s&{dceyh3S)WaKm+-fIsMaQKm=8Wp7k${WoAeoMKPHu$wEe+1| zi?N*;D@!V1a~2)TQcY8N3Qr$O)Vy#dWd^nPvnpj=F_GpJlH@2Y4;>o{?LVNysnK{q zCxaQ*Bszy9YJ`Q_Vf$j0r1q9 z+;oU|cumM_re5Y%kL*gt?rP`78K`c|f9*3)o3$Cj2Eq8!^^1kGrdCmI6VJX~Q)=RZ z#Q*F_2&4zgGXvD)DqhnU`~v-W47kLNZ13YuJ(!CBZ+UH-WrI@HWDFybfd*noSMGQd zzZUK*kNA6x7+MR6dHH-#QYCB5=Gv~|q`FInwCc@wa*dLfYqBmeK~$V1kr{5U7MH<` zLHx~f?i&YFf*t7AO?K5ZM%DU6mkd?4&N;G9H}~0Z(c{Nk1)Z70xH5}2i=r%NCSKdG znu4hg2ptv?TZvD z0vYhaz0~bYM%z~ku9*oM3{W_Ec}DU!Qsr#+z2oQvmDRKQm^eZ3uxgud0bo^d! zuv?WRf)eGDJnI`Kk5c1p*uzhm!m{CU4o zcWGfy2Dxu~lnfFst80Q>KgU5_=;@BnHRDXroS1u;;BViuHh8G6_T1)I1^+Qby5pVLJuh)0Vnklzc zZxPW+x-GoVpAX(Argm2HI(`fWni|U)-)^2cf17oPd`Ul~%NEnuvEbdZ{Ldm(Por^= zdr3^lx5YYB;?UyY`$Ih%Uf|rZe!c9N!4)|EO2T7(d~`CejIN`9P8HgjMyZH<&c1^l~AkF`S-EQq50xb11!D(<(HTmPahV2^sm6k*pWK zetMp&D2oi+_tqU_XQmc8JDB~lN5twL^$}<gw3zH@YQV3!h9t=_>x~~?eqB_bK`*%Ig65un0uj8R9q2lnPce5sH8#Oy$svTKnvMo?V`OqII-Y?-9@u003}LT}?^vcHh69`FH+tyS5^H z1Oebat-6xDzF!VHS6++Wh_1hVS8v@8-AkjqGyjRTK%v&hjEkTJDCv z-VbWXlLosntv-`@*Y1C0vwMDYxGB3DxCa?WV`0xOxqv6wiv)djZ$!W&_HcKmxDSzM zuiogOiepLHN8dSjZr-b>P#j~svbV@T}stec{n`RH0>NQixIUO>dF$wEXw zi)DdhroWFE_7Xf?I~=COY^^cwRYO7(qLrhLPC^?Hi3?d0=Dt!LUZTTlOoXIH%6FPfJxZ9C24Q_pdu zM)XzKgl4`u5+2=44ZY0o#0+T+uPgCRBFiZZ#zN(*7>FafzQ|l%&p2L_uLaws*1Nej1Dd=WOs-d4=sShQ$*00wL5jy}{- z#K&tD1lXCROINkiu}be=1Sde9861QK;SG%>Z?8X=!ow8;Ew@%Q#~ z3-2{TrJa0#$ylW#FJBkv08xRXoyN89Hkqy{Z`WlG)t2kpPDAiqKERBEn{>69E>+i+F&V7PE_z<)CW9oQY2_st+jgk zd*fOOA)1;H~=oUG~cRJwm-lfnHp)(za^&F}h8YRWm&W}GzYhe>H`u;q9%TV#cD zwTfP7@@aCtH5*Ls*)v~<^aWc3Q9Z|bUA-e~vS!Q|etUB?Ct@p-Ji?ELu_?2N{aKkv z#9;O+JcPcHLAPT*hXAN`iY&m_;dCtixnJ?jT0NOVJ&-zOY}0SMYT^5fml@1bndpw7 z?`wl|(I12XB}*qGKM#eQ3=F1IferfH`>VgVT^B=EOmCh^f2jDOGQsbGh(nT1W%(2W zXYG~lNmx}fc57iN@$#Ggbgg15Ae^Gj>k*5?2)ROIl(ekhXZL(W7X z;{qW?L##B*M`A`U?HdEWpfm_S1t|z7=@^lJVDlQ=T|(6N9M>#TE5y;k!8DopJB<4r zflvmb`b`1STz~GR>3%ccVhMTwNuzW1MXh1CtfXX*sKcEmZRV^K zX`TRL-Xp@u;Di(hn`W)jOgnNsskU2D%7fS)$H&2-(ZSx2LnnE%#5>^duC<=pQX?Vo zmd1=M8ECZr^PWfk)z{SXh_yit5yxgfZLK<4t?B2_*8$RmNO=$kb?lnEk!X>qNh+%U z6_u(u1px*sp*+ZonEe^LrDf-hSIZ)|12T(>o8&dC|Q!sLs?~a(oT3Q)c|4BB_qe#PDxus zKa+DCuGC?CpgI@n%N%n^J9!{dg{y@(-OrgIoBW~i`u)bA-DgjZI)mQ?6-|Ymc=n;e z=^JvEZryRFWEaD;9N47s;oU%fv5G?fuW%zn@?i(5%@~z}6}kM?;LG4RXn_#0lDPcI z2K|Ty15MV%dtNLM-9MSBd|fwwwsB4c3?vtRXR@D%?LPAL3BC%* z6QN~exBE!-(}{m8)l*f%O)F{_WUlzgMOoS;UD0E{!Wp4Hp=8h!)^VFwKE%~{S?%Mm zaT};TEY*ybaAg4kpMqw|8R(x@+Lo+gt#F^HmfP)*M~0vNI%g(0|Iw&oAtQFEGZ5HV zo6y4UD|VuRxLcibKS+yJ!;>W9-JXEA_3SKfGVcqU2r*L3*TO7E?m{2wS>Mx+IvN_J z0=f4q^iDFXpWz9z&xtVZ+TcXokE=zIWEG8YAkxTSHNY+0kWZzMVYBm@kU^=Po<3+N z%gWa8=+lAmnsCnnuE$d^Clv~WD6cDa$V@QUinax(H*2dFU89>js=4=qCX-jnM7ELB z_GL0Su`I$FZYQ;(8kzW6V9ZhC^^bcX01!{UsfpF&q}3WC|n-eg9e_}q*ELr;IiNFV3 zjXQ^d01N(26vdUG?)^F5_L}*TE1#u&&xL|M{`OAWZV#@fFo&_H^?xvgKd*_2=U-Gt zcblJscE-Y=iY)p2+Y#pu`fp(RyAQh=nl#h-XCEQpaA%^nBkhDI13^s$8_0CEqUC`^ z+s|cVUSn0KbUAXBBKdfE5N)u|r+`lMmSX$NzM8QGS*pmSFuUFG*Yw&0?&Nv{9p(kP ztmklpNOC%MJHl$E_(FS47WwR61=i%^QALO-u6p&d_<_zRW4}G0+yR`j*E<;u)X|b6 ztr^O;Q?Q-RK9g@8HOH0Ik7)E_zI9R6yTCFDfVYbklf*tu4i%_3CR>}ZO~k!hr&o}N zxFrAj(8u|WQyc&PLE5s(-$Xe7^Qbd*xYH?@+1KPTC4&m>(**MDv2u=lMO#L$Y^&Ko zK&K^&LP@$oY=0jck>^Qp2(iO9U*EiyZVERX-9c_6tsuit-Z;Y{pM zxcDx9;>8?|!Sutib={J;tDz5JJC!SPo4&lQ+-|6cSjl-2@u!#64dGop4I$l>VtR+0 z#n^ycn6CiS9*B*KR(mW08f?Px+{2zT}zSs7C~SJ2i( zE)EElBHC6O&=yw(eD!VrX_;r`MjLA-B(c!}2s%QBBXHofJ-&*XhpT7s+_f=7Oec+0 z2cN0v`*f54V#0qTb#2MvEzDkbyg66vzZ%x4&uiU7Mt4?42Ne(v0-0K9BCHp`^F`a> zrcHb&+s;gpz53x-_t|F6{$03YpQ29eqZAd7Evvj?ni1YH5X9bzGx$6NNS(oe^%<`QtPv=?)0*OGug*ZwOU&^oEW} z4U0XiYjCbK({tX(`$rTn3&7Q@u0$?814utHc6K%o^!(c=e$z`PotNwR=g!S4(-&K? zMA8@=oaOGb2IW;L@}8d)8;Y#cUiEGG>Y3uaS|D81dUZW|0QXyvLh!#}Fx^KaaVS?I zD)w*-*0pp^UN1)XfirtK%mkzq#e@)N0Gm8a_y^PVHTdMDkHg67UmY zRN^L~!95K-d~BVz&Hmu(cGrm!krOb~Dlbj;37&7wjJ+yD)LHn2(v+uDh&WMEf^V%X zvVp!OOxz-SNCdq9Haw^x)s=M50RuMw5qqQLm_5)t+knnt4%tXzS5KQ)u}5GcABOc{f+jTC=cN z%1SR1$^;VV_a1N9;R_Atx3v`&Xs_{6|v9(4i%iER+5W5F7Jz!Fgd660!|Bwfj z(ajIcCsUp%Ksd`;tGK;qM(&J%n*7;)55S!3QPbV5T1!=?s>1m3U*C(>)O4@He;`D8 zusy@@5*D!&ADTxH3U$QR+7r zG2pZ`Nz90LbOo zMtEZ#1p&@T&&lIpy>w_b5y}j8L1W(B+7*I|(#Gz9NrpDVBl`rL*4=~sZ&_KFV8;7a zpQpbw2B=kk2ldTzC>jN2L19KEGL=S0c6rY{MIFj$)h2-aEpzHAx<=4ZX4PJ7#Sb76 z(oNOWrYWY@dDxk$r``TU!1kAfu~N*Z&6jUg^)ZY?EZzx^u#)RncB JDivNq{tr7Cbld;{ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0012.png b/cinema/gba/window/zmc-window-mosaic/baseline_0012.png index 4e4165d5eb95ffb6b03be0cc219e3869ac1d1045..af7e92a27b7243b8bb39db6da488da9fd8f0a54b 100644 GIT binary patch literal 4518 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 V1Xoyna5iWI2D&CXRay_C{{v&!&~pF) literal 3886 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 literal 3863 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 literal 3858 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 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0016.png b/cinema/gba/window/zmc-window-mosaic/baseline_0016.png index d3636525a0bfe1de818d0dee5349fd92870b772f..ea3fa2a169db619a82095f59c75963f0e257b4a6 100644 GIT binary patch literal 3982 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 literal 3780 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 Wd4fsX9Dn*dWzf??z^gPLJo_&-!b`{i diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0017.png b/cinema/gba/window/zmc-window-mosaic/baseline_0017.png index 2d6086460d2d57660c138051f29843ecc5c5fdc4..1d58c39787a292df1f457f9e6b01ede86b2b7642 100644 GIT binary patch literal 3737 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 scQc}i3#@*Ze)skJo#Y!<#sE$cP8JpxfT@Y0 z?a{h^H2&Z?b~L{xhN&I-8%+(b-hA+6wdfxKV_Wc5;=OvV%VI3@BlP@L~HSN@b^MW3-`3 z{Q&4WK%Dj;8dItS7A`3N{K4>bJd^7NB8~)7+B{Y8Le2ia#{gJpGbLkrfz8kD1rj_=t*U{J5GoB7Vm;w?%Jo0V;Tj zY?9VTla!LIRpM)NN~V`SJIy^zrvVOd`=R>I`DPQaf!`Ew9D84@t%T!jXgt#5teZ#FkoKo7V@QQz_M}fcdDP(V#QWqJjLMWUUx? zFP7On5mNwf6ru0yxDopaHc?OuZKtb>I3vTvdLcC{1aASh@n-G*J!&u>R5RaI7Ysji zU#~}B@D$+QW`6CHrH;x9l41!;El-R#IhfOyW4Z@{m&DtrEauY&a$w2Fp+m_gLz*Oi z=bVcDP_pr2HH5wI8b9wK4s*OS%DFDJQ@?Zld+&MwQBeoh(F#^=4O5j30+wH{Cfm0JL%KY~FEq84XT2njgoq0A z+9a4Llq<+Q*Btkrc`>3zZxjo_$(rqsEW4K;frF^(v*3|;VACb${B-l=_9-v9nY7Dt z9lqOjCpoNA#^%6=!rK9}y8i#%uJ6mpuylCs-_k2gb#2N+$ zlW0`tLGla8cF-ten`_u31-be%k1F`w+pOaAx8+2lu=zv#>g;%5EDK%duT;x)0tA%qX{_&ehXTjus9YCs!CFtoXwt?J&8AvQR`u2vJX=cS ziGFVYdDm&YsIr1Thopw)jUsIhb(C9I&EDTQV-yu~?wJFQKO?-H$L7ayz5*omH2Ffs zU`PNU7b{=USoR4{{;@N|#GfqB9xK;M77&@gu@B_tvMhraCY!wTd*^+!8%A$(fsU7) z7YEbt8h{ZI>7qneqYF~<*DwOXe$z(n0oXA6=&S>FlQ-VSuy^i5$gxPJF(iyiMGO|zH4$NG=g+X!vBlXs$jBn0#*mlbAojHj!5tv@%f^o!tjJS84Q!F6d$ zsafJ(Y7B;-Xo?n*IwGhSrHpLad>*wuyY^nM8Kx;dYmC3Y|3w>2eZ&FKQp8m(kLF<$ zZFsq5jcHw?_pAIbj&!FLLM{tHRlY21!l{+ADIEP3wx)VTx9W!8J}f=XkUR?b?!w^| z<7DTGfPzKfiz3V+Y*XAg`?Ro9Z*eb8n=eSxSN)-Iwiot{5{QamKUyN(k_Xn4CB=V`ZGUJffqwX&6UGgreFB$=HFD{MTWJ`2#x6 z5+R=Pj^PagjaHlwOS3$bGRb0}4pd`IX?JM9u`f+h(daqzJ)Mw|!4y5pCiYsPKrMXK zLmryjUP#+Qg6uF}iw+pZq-NO8+}KimT;H+n-rWKTNSBQFUACTMrCzLsj(}fD8naJ@ zpZG=MA%;A~j{JW)S*OBbe>y7-NRnz;fB(kd)`^mC$!H9YO%r7*(pvQrZ}0+OHbIWy zmth!mNV`>vqynf`%DW^-6qtpC&=fN&lB9InVXmlKpW=*9oh-Gdw!1-?qN9lxDG7 z)p79XXxApUuLd^WVS!u%G~SjUwnC~}XhjDe5TdfYdwb4#$hTH$ zCNP7xEHVj2Fn`@Ts(Mp!a)7HRdeZ!$APFbHBx!UubTZSs=Rd5a^uajYfJ&7F3qvj! zyKZkzueQ&;*G@|a{qxcP0|S4X$goC;dqN-*+f73R^Z`@GELI1_Rq{qrB@=1tY>q{X zbyBm4*a>AvMmm(8sK2ro;dyoTgG=K0#?FbKQ#+cL_(=QK)42Uzc);}oWT`2nf zZ*&Uw|I}yyis+!ggMD?o*Qzj39}ZQ59hFns6gn*Ues559GG^EV$pbB^uk#DBB?_mF zXogkSC>?e&M0c3t=W|KYKh@D$ZC;4jv3;>pu1Is4jxvosiz)jjMh{sO`A%ua`~tg8 z{B0jMEZ_}a@+(BF`6u3gkj9LQ8Q$aKu>aIB51`hVf$2%#mq{VFB z;be7k{%?i(uNlzR%~y6WmFz`{F(7Z8qcD3lXBC$(rO@fhw1Sy8w*~$`DE_ZpJu6CH zC%*XtttF6@xyC=%7lv-`)o8VPHU#PhD!(RXP-i2&{oW5(hbo9Nqhr@vT=CWgaXBd) z#cvZ}XZLC#y#LnNJukPj+E>ga;szw-&e%h@Wq6Dxt%PD!Z(1@QsQ%pjJ|Fc7Bg}7z zU@g6>_!ae8z}{Xn4V%JX55vt36DIEFwM-XL8YWyA%ptf41mkoI-B=5D5m%{tg}w)m zfEY6St(+|x{eBa7r@1FlB<5VjcvTzx>@6{lc(xw*W|uQ}=)!fQYvyJxQHy+^_9$C8 zk6AH=;9KoNG@vqu^bDRf+(sHBDcX@bGNnouMmJkigI?x7p74%DA$zvT8|HJMN&;d7 zc)#D}`%xR3!NE2Q%DJ$Wuv)3xloz-%1Ejr|7D1oleRXf3#-T@}I%*O4d`la_U`&Fu z=4h|SmOA1{@AqOQAecKFZge#X4U_Djc8AY$RtXoaw9y4Xa!fO| z&sB_LzVzyS;iLm_F&WVHBj`YE79^VPTEoZd931w#lG1BV{I=T-Hc(Q4Fd6p$vGLbYXeC@*X6C)OegYvw9l`ZPj9_jsgN%VmhD;>Jgx^$T31zYyOM^lrYZ_}2GGxy(zLCsWLfNt}gY5gRLCKy$ zVvIGWv1AL?&{!VzJI{HZ``W841)XQ4G4C2 zb(e>&N;)CBCI4|da~SDFMwkA5r;tZaZe_!s>mG!No91zSf~K8-+`$=Z=^>#1nRYm>jV;;mi^&=DMZh36m)|%cpufh4MYJvJ>i<>Woj74Uf zzfv)MeVEf3GCr=qx76o4CloR?Jl?)*-Pm1fqZ*#v2<*e01o~My+C`Q?krn#JrU@5k zH(2b(TUMUi<&HUDu;Ed1pM7PTCSB(uG^-dvZ7}d`YAD*8jJ+q#GWo#Xi%(1MVC32; zJlec~jIl-?sP|%=-k}VCQGuHI z$`C(@N5=wYbr5YM>1eYFBa#Wsb7JjgdT>DN`qwH)i`!oGsL~;72x2P zBYbTgzLV%GXS0&{-#bsda^LA~hAJ-;HczlxRgiE!Gz{sd+|m#1O18`BM9q1K0 z14t+Yaq?>!adceBJtg4NyjCk|JgnV!`-^c2xfXrzt;sb9so*H#%e9<%r{1}jP|A>) zzAJMqc$W5jK)|orO)OH^=!RV^?#4)w^&Q9yW-xz2(MogiC66)+IA9lVE5-8K(u9iVFbOIydB$qguDE1;t2El@5jw7cuO^rkjaXqKr|MIk&jkaqsO3yrp z{liGN()>Im>7o?Z_Qc0n&(+I-rWYQXrRKg;yW`8wAm0a8DfJ&(mnj@Aj=XbP8ed3q zsf*|-mq31Y(w5M28qT&dXUu}i!(ja;pHE-QNbwa>OG8TaLBOgG(>+VqqoBG|3m!SQ z;O?^E2!$8pk_^<)F_$`gp$>Au@sWKE9TEBdXWAJIr~`gZKD8#r-UA2#+j)tQ@lY>0 zLpEuf!AZ%d1D}M`%SVfm4<<#lGUo@H5gBi*jps(=E%aS% z%3((Wduo>tHL~;^iJb94B8ziz#rnKxUMfKbVzqafM0}<06sPaU z7i7gbVW2ne8vMqj6@U9hG<)aOL~Z;L>T?_X+~Lg~?N_;7e_;$SFm}R+Kl&!IaPMh| z7dWIMdHlONy}fhq{L}qnwfU119Bwt}@(ix}Zs8_xocHz7B;Z!6BvTl3Zvwj|ey&R| ztTkq3(#N4UB*s(Y5u6EDjSfuZCyNAfD^X_hFzZq|d%9!j_r!qvhk2;QkM$obo_&-> zR|&+SC*yn1;3M3dhn?ziJ@sqac#nLCm;z^^(5E=v;;Siv@RW1n)l(rGX;r>E4Euwt zcN~yT?Q<+SKzWgBD!9sJ^uuv#-DHZlqKOvS^(!&+%i!mQyA4_3oc^iAjRP3GyA}51 zq_U9GDZftpFLZm)&I<-nMVj`$)defk%w!1*WxZPCh`RWfo(}H!kLAdR2WgIsJA+?| zKDT%5d?m-OgUE%`uNx-MCsgKwgow?MXfmsds6`wbiV)d0hYSN|-kNkARlJ4?63_2|I#QterX3{O(V93se zU35`dnY&Vu>$l8~y!<NG>(UDE-Z#87Nz;tA?`qDLxIE20#Up@ET zj?SPtxl15&kRWh~Z2!Vi?jU#e2~iHCGuv%L%s+S=rz}CRSaji7=gdW@6+SFPp(Cu8 zmv&K?!Qc$usM>(mIKzec;T~*HMI*jz?#~sfFQJ5Q2tl}%q#yszemW#@%s_~fD?qk= znTbKuJ;o*?c$%gdw5h&yi>Zvpws74Yd*TfX!6tGUprL*w-+27gK>=biu79WWQDYi3 zCtKz%rMLw}-E8XmjF@`tXWyE0zu=bxwjVgRyR*NbLZ?;k;pAuG=u#0=kuotC2MC~;Y*o4b zba1q;<9xSp=8Yg2V=wD_C}a3~V(wNZ_q{AE{ik^;LGm%36N^H(JMs^9-|U)oMr`_8 zN*1Vg#0`SkBH3uiKNNP4O4UoZ`n}tKIzU>F;P!2T!Ttwo+~eO)ZS?lfo`i!^>#Ku` zr02}auw-Cp78sm9)+u>EPf^;>C>AHqrrde?_Hfch)e|zZ&)uQ@r-#Dl}$wF3>{mqEdi0Ezp)d-XoCyt$yBl`NkaXgAZ zq$qVuo{qpCCz9(hljjGG8WKZ3cKkyH zQ}i^qb}u8$6CTdeg>797>wXGVdl)9x(aI7?fb|zE`EC#AeTtiO|4s@T0*3sbvz7d< zpK65kTA765n@hPDwwlw29}z%8}eITh>fsrn6nl-m4qu0h2* z7#vvs%SXOR<)x08H z>KFKb(ngk#qY%=zw9&q)_wA+n!0F^?LA#;-wj9d*w`9`~wp!2n~8;5EpD$p>TSD>RI%3PH?KR6s=E~61}zW(lRaQxE)oJBpoW% z4LWT0NSToMH4nnAZ{@`r8TFjxb`{CheumTb?W*`bPo0m90}f$a5LZx+r-KUdrDIKz zvr*NhdWvsXKSjxrW|Z>;R#+J34$VpVY-^ct zh8ktOLkZqi(an+90^-}))y=OwqxsmVpm-RIcGMDY`!r@z=NFD0jhk8q$Z}2F(Ek8e CDd7xZ`(*3hu`ewWD2nnO2*w%IA9IJn}SYbrU|qq zuL)G7P{}_~rHT|nDwYx49Nny0WDUaCn0E_H%*hMU!eaM|(FY|_FJ*pM%H;RJK&TmU zIF=s28P3ZgpJ|7Ow3&K^t39YNmBd!kXu?*XG}z;EZ9lLLA2G1C4->XqY|{t)$B}~( z`(afEw@mGk#hk+^M~o=%T-eVw>{d3}ApVSCfQ+=m3d9-bv8 zR;>5$$?45rw7!LH;WQE}PeXZ$m5mX4!Tej7ynIAyxlf`arjAWt?kT)jg>y5m&NkTh zy`27A|B~>&1MW+I*hXjoir{8kePf!cqHOtoKM2F&?Jx`zwcbSXNul%M16i%rQmb|O z(Mc@7c9P4zqROu?OD_jyGPgO5qAh=PO*IYUjcE>ER<}P;@)%mMGLpXh=!nhpcUHXo z+L@N0T1(~LtlY?1KC{g@JukI9oyl&79CvqRjiH~6n{m~DYBs|t8ivuMcGNYMh@5_Z z9;XjpWxY6Q5vOA|6VW10$808|MVwwP_hRMVW6NWCp%|&P; zs#gxr-&t{+=hJF&;`*LimUy`mRSe6DTbojTQa4V|6KyNIoI~~qeQ~vzUwC^~WNd$I zg!X+;Z0NdwK}5Qy`a$UXUj56MkI);1PMn7(B62#jci3*fFE-6)b6y{xZ%#CLD^9ph z?~HFi=ytnBM85C4u2-Pb7gs^kYW0_AmRjxhe0xQ7-}i*dVnf%|Fp7HJ?pa;qg-)L3 zh>xDn-_7Q0t=7V3B62!Z)y1aU@0-o0@Y}Skh3Ff9qB7WL6h$=*lZY+~b-M5LEK|{@ z%241_uiLe4Tf9^?WhH)#AT6{GqiDG`bHz);Fk7u=d|*vP7gxbb$u<}o z37frttH^O9$BhD?6qPJfAtKLpd)=<~!3D z$u@jH8~1tvi5+im15H=d+Vr4N4xUz_D(#B>Ky%D2BULa*jl ztk&g6=klYI;I3tRj^jB_{M4{Sn?cD4r^9d>trVr!YBdLwX;k(e9i69piI6N9OakL< zDd^7sJbK-(AB3u+%zuc;55oR-L*JBXyJ?(Wo`*hvXT|3_;^G>XB~B3Y+RV>#>?}fm zbF&-|4`p?WZ?_U~vmC*F#)A$fL95kNl$v-MMZ;dNPegWGA)-zT&byb;q8BZUiu%VUePpe(2zp*S&l@}fn*q_ zDPl84UigXBP>Div+^E%VfB%}h8BiQyv!aq1Z#1ozIK$I5RoC_H+t~(8+W`)Lwbr27 zNMjfh5qVb;5vhtIs#00ES(FdMXnOLuT{~L>p+Pc(N$`C==sP`Ov-y+K_=!R_GR_ps zR63oGuBnxOq+BpGm;{!YV6*RgA`vJq6HRSrdvJk2*obMFCJ{xRakiM9Xf()iBfG71 zIvq{d)7l_SPq2BO1ti^2wQZAsCffj^K{EA<*6FzM5}~i-`gU8{a-ySN(K_9Lh-}+l zWJbzlXF{jkn{>q8C6r&uS|;pOZ|tv>tIur9{5r@ymn{wgvTb{QamDn)H{IxTdqia0 zcBj*MtEJASX%|62L^V~JUwbsaI#gu21K&4e?eG)&5A0H9L}e^J#-#!tJT2w#l;o#I4v#} z5?3c{x_&ZFU%`5_MLUgHJ1Hqw_BI9(x-jGunr2RmkUTFGS5|Ckv*3npr?K1)gx(8e z6`F_we>FEKP7og@G}BIhfkI;p4LBO(@olx5I6b$$&Em~B8`^0+rkzwEG)O@RCqi|+ zv@UPVh}1Wb3J49PBG-_kyxVQw-*f4p!wUPfL3X-fl4H6d z&pgJ_5w~;7dPKIeyl)}Tz#wIn4O`1UX)@!QLIA!-AT*E)2o0o@unQM|a%BqhWP`p& z`1~dR3y(%z?S##aO^%M^FRQh`#b5rHxTV*Bzxu=;_bY_`oDrdcR6uAT6%ZOo1%w7t z0il6ZU(&OKMf8wq@4Jp|^mQ8C4XExW^z1fGhNA%*a z8=d~ka%yk|-FKN{4i4Wt4> zXJI`2b=O@0$v|iz6%aZj2-5q>BUXKjtl@gTunZ~_HlN2o5it0fYdObL3@btt+4p>|X+z~|YwSu#zGWgJI<8jf@%chNnTtmw?<2qAjvD(1ga%Rp zp>u&Cy>CG1yuj(b0zwx6oZcTGbTPo`Jpn=&$8SWx?Ya-&JP^8Qp!H6{?R3jw&FS&* z*D8C@MNkOOG=qD}ogNz+uIC7kN?TMo%8^QHoE$Q>_n)~tJ28JIZr|4xu3L90k8PZO zSn*|=TT<1z?`RxxIlNkfJKVo~E>rj>A}VK1A^%Z5B3Y`4mL zN+rLdwD?RRn*au2mte>Nqyj<%smLNUdk=g7c1gxNC&6j3pJ2!Vqyj<%sesTxDj+nF zif8p3`VYJALjteZwSDE0=cxO6o?PDNC{LykfV~Biu?rn9;(3M7_;5GWq?5zs6^DY) zzhvqW5fM$YFIG%pSNXhMW$g0&zWce{6DtqUE0ij*78@VUUs0ats^rgXGA zmxSc=TPgSI%(3rX5HPjJTJ~ov-ccj-zKweJ=LOylavYbkAGf?L;{)3?A~cW+2o0nH zLIbIQ(4ZK;(~}<>|HMFOPz<$NZP#5O%6vOr09H^{^)B|(U7jy8b4(CD3J48y@tv;M zYBg0=iRjdSvcfPN4u_%Z+L}6lPY^{;5Il|35E|s-58uD@{X3#l%erttGKhsy;6QP2!y*= zgXHo}JfE;RotuEm=LPJ(TAf=Mn3hT6a*&57T(2g7H{8BFum1U#slEUCCUeV;!_QoG zh5L<0#C?(jHYLmFO;dVx&n_ulW;h(C+AJ{JQKv(;O=dgtIuuo_%6Pn9{^!f;voFcz z;{AyrLW5%H_xrY{lGR!%4|?6F>;{Aeh2r$$uYxGLU3<>0+f{5k;pw-JEl&jV(4aW1 zR{UpwZnGCf^OHYQ)4V9^bcl$2zd7G^Q~6mvxqK548k9oN>#o~8$F|A$oBe*@Sk5m# zwtO87IY42E;qrWp(NM?<6Kp1;PKSu5cE-=};^pFukbnO=5E>MQP`NRi=h$SkscBKQ zDz@Wx#>?Y2pVhTLU&hMWi5mz_qu4B8X?Gw)lb^Smb`b9lN#I}}M2Jh;=csMBpQt^LMNJ;SeX$>R*0000!E5747KdNYG#;6Di0o!SV%anWX^1n>pt;4T z;6opL^dIV@4|6a%+3aNmfoTV(L3EZK8Fo8Xh-a-f)5{*pQDj-tlU;J!sr){2u*>C= z9Dk{l-m5CTuxd5RrB-1o98?zh6TmHY~oH@4y-SQcNtFfA3hPg$${P}GN5Apl8>bJ@A_K5kq@3;?9X=FP2-4-VVz}^CrfC?6WFSA}~y?-wsTwV;{owxEI zd&3LSI^X&++y0g3od~hh$63Dz@U(c5npkPQe^0}~{gd`{czk(vwf=jEgTzW%>@=~G z*+V}Nee2Jp9PEtR-wuh$|H{=nysv*d-}Dd#gnl0I-7D8~{jl>6nr1uiJ^Ai)Yy7X( z(`M5&jDq!lJwFJH={yWlv_8l@$@GiD;V-{XtDP)uwJ$G*spVHCm1nl^UG2Oa?2^dl zAPfus2-|d;;4ei&~@YewDcjBys+Iv zKOM7;h#vaU)$*%Y`PGKyh1!q$*HsJ4DhGRr=Lba8a-4$wbY2fFX}y0Rw<-#y#ffWr za^8wREmD4&R{kzl9{-Cx5htk@mQ|j`bLgSnkCW)Jb8M+Jm0;5z+Mg@~skSAENZ6+5 z2mZu=ll^o~51lD=Y8;w~XxO{2(RE!{K6D%>dj0BRcoM~r(@b55z0}G10FTBq5xUc9 z6Or%xt~)N#Y4_vAIcvM4mB>=N(}^A@ME572R9QX{wi$%spx-}kSd{BSdXytRdVY6) zAFpk-A8aO~VUMcI#Dwd*j^jwbo%7a%=o!6#GT3Jf!xX|KqLQ5+4bGZ|y;O#Lj|TmI zx7(FZP1{KH-?EdIS_ffRFg&6Vht4|hdz-;i#P@w^vk*-x)#=WeVbm#{(lj6S$tqkMDE8)qNE5-i=@qe zo)?bB;beT<;C#wVwiB6h^WZh3XM8V`PV-ipUM5-~e@fw+;) ze3ZjtVvCQ!j9Z%iS~<#br@Zp~=9X3ZpZLF8jV9DI%^trllr5TcCKf6=PyGXL`1c%5-W7yPbcEU5B+qjn|Mj~ z)99Nu;HYK|2BR>X6H&iEh-@DJ8iv#Do0Ve>(R4=RFk!PiXx1AaY|e#`a-?QGE-!}f z&XYDvV6!$zr`zo_BI3diGrU&H3lhQImj@$1>7b7l}NLmW<12w_jBVGvBe{?kt! zwD5nC6t<6Yq}mKLLo^-PH&ZwchGYarIHl1zYp=G~@S^uV)E9hi8ivo1-iskudF|Ns}T#XpqiP&Fb}5W-&;8M`N$sF$xAc zj%rq~KOv%Sw_7kXq6mb426=S5-Dq-!>{)F~^!fw&Qm@xzqtQn-OGcOLbt9U46wMBm zS?-DFZ5q|i0z!j4JUCS=cZ+pZad7hAJ0-#AKCO*cI1}nriBM zHe*hb&p6zut|?4xU+-5BC!Qy}4yXNPPp5(G(^4u-&fqzupXPIW*gVdj6e*OnVaB zlg$bxf3o`WA^Sc5>#y}1sf&0wLOmC%ev9kHOtl66GpJ{L7xe1YVb1Fzx3A%k|M?@o z!eJ$47rEO-Ia>%w_%-|`9&8OP!P*J04)VN|{CT(A#YoQ+2wgHoRF2n#*5v}BOQX2T z>+kyWGqf%TTojf={@9m)`)j4Qg9r#+0*W>-e;a|*WdNa7DaGc}n|iiDXeFTFz5$_u zRzT?OLHE3eKxm*9D{<&}8a$$2txV_Yxo0H`Bukmi#w!cu4bLkphj{fWnT4JPWhRhQ zVQ+qB8r$6SFP;yt#*VSw_x^3S2|y1Gv;snJ!+Q4&_5=tGv;sl{t$@%#D=^z`$@Pr6 ze<4~!`POpa5rEM9#MEOdShyHvz>ouI1%w7#0il6b@MGxdyc*BB-mR?=euPwKfeb8@ z**=(E=T~^k9l%}0?&`ZpNka>(!?nR61e4qLxa7JZW)$wz2Dc)vS0BFHC=I?3y8z^X z9vWx`gx;2&&qM;-fUuoj2!pA>1qPowf9RnVKqtL_Kxm*95Lyj%(t8GkRt8S*6A-!# z;Pl=Ap-Tcz?*|aNH0HOT=eM7~^;tZ3URc7)uVK%Z`PlWOx8#7!g|ewmHkUe6-mjdU z%ltdl@+-V0<{Uo9KE|u>QUd%T7bBh%Q=?vIa^r?!=2!52Kfl81+62|a+HdyTBxG~r zJM{sHm7VbC|5EOSu=tB8W3LEFmBK{Hyw3d*fK@Q$0F!`M2Qa=g+ zML-V?wBn^O^OgbM-re3Um_4#)wDSoJwMu4Qda@uQviNi#-&7CTP1EO;yVbe+s&94Hf0GgDp@CLFXrL7k8fXQC23qmt zr;l0{BBBX5#eepIyC_t@=6l`vw)cMmL>dUK6Sk2IK?JMwp$&tX9vw_3`4vo)SHByt#i&#)#alALCD?TTYc5{ zPT6M2u}}|j0k9howtIR^r|XlLmVCKmZq1=RB?x^~%j~dU*NAp}PYnR#Y%}DjR0;|D zn%Rm7fb!Y0hn7xf4W^+o_*N(lIi%KX2m*jd1Bn%&6%ZO|1%w7#@m%822i$e)o+rRS zL0(vmqbU3uvzh8<f z*o~d%rTrTwFRi}7UVT}(%I@^&p+Pwq_M2~g3PcbZltne~Mj4OCbJ;lw>>ATF<2M%g zusVbCw87o*vm_uiP{Od^RO@xqG>Lymw9bPdm`|s{c-*zjsE!|oqnpX&I1QnJ5~2So z^gm6BMC;reUJy|j{I_YY+`>6nz4MkkdAR;|tVU}2am@aYvCjUO`>WVfp%=52>)e0* z=I7UUYRnRWyV)cC%X_v2K|pCXC-`tPX&Oee6B5z5Z@7~i>9jk!iI>9mWm2o1DE9~b$2<)P z4N7G=h(GhgFj%{F?YM7ryD2xc-LyOv3^_n~wA%5X$mVevMj?*VhBXeCLkQnX~hy z`F)5MB&%}V8;%-~BYzClNkl!EfS77KdNYM2u00VrY$IYqe>Wl#xPOYRPj8 zr-(xzIP!;b8cnbEP{c7{`x$J~bmjLc2U8Sf z6TZBZ>s1x6tV)Gasg#%sN2Pgs1(t_-DZN7Jjh*upEc4HwnJo(3yR4LcDC}wyn9iAt zd`;t=r}*PG@2AP~(+Tr&KX7lNR7-U7yDcsVfV~Bi9t#$K4w9Z|ynn9_uCMy%WI6NURjaP7^DM z5&DVnQ-2}lU}seRc1%S67p~s%WBvQtrl-gt^vi$`uUzE%VdnvwraO#Kfj`AtFPiS2tgJ1+;jB(yn@ zGUJc1O{Xr->-FhzIs1v?`_PJ&p7ixqUmd=?Y(&d%9?Q@2l_#}-ebq0$sS(kcI9qDx z(ZZt2)0sao6^@Q}9-{9acHW2H5&oBQ!*K#BClk4U(F=ub5>elEqj6gK5KG#y-BX;7 z*hWN8adf%-CQ^R0VR@$Z!}z*vVNvB^5Apnfh#HQQF;1sNXhq}w`>0jnP+F9zre~Lp z=))}K*Ky_VBIVJ)$TM*kYhh94NxXyz?S34E$Ihvhr>PY-jnMvRF_3Cof{27|dVb)K zJQb&ZQzCSt(6QstL`410V~w`!y6VtzobdUZtNvLyejI1&I_#}Z${Tn#UWm}GR+ET) z-*??%flj+0N6tml9W0G3HCwIlctv!7Pp5 zqxobr5%oJ%UL+=5*L566`R!abopO@;&NxyX|&c zJv41&C4Q?Qt+WoL%s4!fV;nl^x*u%@-y**6E1QL=Q=v|`E)1heazc}Uh{zVyGL25B zLqwwP2w|Is5!E^xp|hU_b2-2h&Z*_PL(dzkheFhw&5LNqnuy$wqm_~@)Gbmrd!8JB z4CP=beUFSPUDORC@`i4&+jWPYip16GQZTMojqIb>n~XyfQKKDw#P2X(?mp#_RUp)|K_S6)fzT`yC|oFz*i3qqa1a5xRuGn5vh=@ z_%NCau@rQb(?rzkc0E5Z4I}&^BF_(2ZYYyrhV2%2hs)!^?=BnBv5u87J9UG&k8RPfFpS0MuavW1Z$L!tmO(^+ovu8o zY}U8FosQZq!}n+~ltgqxIe2TSeTPSdVm4RH_=Rklj3&|{ol87F><(~b7ZQz}j) zHfz91#p?A2QqG8|+wFxm4}X(#vVF6vk0I*LU?^8?Rwqq*;gij&@S_~DS&!?h{=3Un zn-#EG8V8+YZU$@Hbh1|Jm?2pFMs%9knhDqQwx0Qx`(mlZgz1$(MilD<{qT zUnGX@M>%3`2AU!2j*87Bhe4l=z>pIf3}v&``u^`(cmGi~8&y*M4dFz)x?-p4~?)^Bdsj+Lr^oY*c)8 z*9yN@JLy1ZkcK_G_shqz+Qo+FE!tTL&pCn6fI$i8myg4ejZ{u)r&yI05E^76l-@0S zIOY=cCzYLlo;k<$a)Of@3mtz72o1CXLIbThC?7m!zh{5_wO%835${Ii&xPjyitG6_ zZwvf;$e;0@)8XN9%JY)j*YL;x{42e}aVcgOx%+uBTL>ulHT(shYz-{I+6jk8Xteu7VKJolefgKa&h>wC5CWkKAg9fX zM&NW2KeKftzc?};bk%i;) zm^8GoS~*W4$}+uz$0gSVdFd5=+Tg2*tL3N9HcW%>!!7_RAVLGJfY95r^O;Cs8z3~$ z3J9G81nK<)LIbUU(D^`+-ZLO{Zs7Dj0ilZkPVWs6x**{6egL5hV|IUkH@m<4+E?+? zd1VPJy@owoBzf19-ijkG7s{qO*<9*0_kQIzSK$w;q*wTfm}|I;yp6-}V&)V`gSmzo zF*T}HX5F`8nCTUK-%qb_zP4I&W$icnX|lQTo%(?7ba%q1|HXV4!u&6yguNm_QJ~8K zv;sl{t$@%#DJ1`-=5}Ov+Ym0t-$9@`u(T3Z_+E={r<;Bm$c5uDf5-)Z}|*A zgFk0luxmTdOZ#IcFP(pZeg0+P?ArAd5TQXa7&a4ho(BjGilLnRMH$aYuH8og2r*4_ z5_$Mxc?RWq&3c~8fY2ZphW(~ot(vAuM51vS1i@@F35LUewq=HOd?^Rx(epSBp+PRB z|EKi-oDhk|rPseAA{qR*ZW_@#fn0itJ9@hRcC1=#`Dw)dkC9-1%KcS9XiyA65Kx@W zD|{G_>V{EoNg^6{4Rp9t$EgD29RSwk?wy&6RS$*L}{*fY6{&`n~8QU&@sZG!YHEM!UUMx_Nml z=yHJKXf&fgq0K`n!y%6Inl+S*E`;wn;Y%H>7F&LrP(O(SHlh2ONeR;jZpC!1_K~SD zW^&`d{`xC(5gex#<`gAn-*=+id#>ltZ2MC#ab015PQhOT_ck*w>h^oxb(=%lZSp;5 z;JQMHEy`1_09_7H7^=G*_AyL@20@I?MAYdJ(ZWu1b{|{*{r!wBUk5^i!cZ!Q;~Ysg zJ60_m*@)U4Est7zTC>8DjdYs{LKD$+zVkV5FM;!#wMci%r;?<~QIFTl*JskrI$Lr?t zNM~m`o*U|L*N*qq^{G`&CXf6f?<|@Ra3CmEA2k#RIxX8lXM>=X80{#%N9R^ zdoT-HoGU4-L#)z6yh%kkYrBzlBot6?XX+!x_J{+DEt@m;PjhWra1~OsL`Qg=w1pw= z<`|6wu9Rjdn#Aw(S0^Eo00B8=ixvkDYnW5P?zt^{^8}z+nDK^cjDXo>F}O0XjU=XpMHVBG!ecY&c0q*c>Y6mg5=kE>4*o_PSvCO zewIgv5vyp@@cJ|w=+?baLX^IWJzB@DELagQDtEYyfLr@zhoEYexyu?mDqR~|XRh6% zPZnmVLK~`v7CgroTXQeX7vO7DG|)ol3Xdo_O}?68V|{;NLBCGO_=-|^Av3N&jKbTz z+D|H0&6SE6_0F)ihe1HzbX7UuPdV6eC!W0fB%T3@X!*pevrb)KA{OGm7|yO#gc!sJ z-@3>yAyKhb;6j$wJR|Q<*0+e~$jQ-j@b(WYGNB{DPA)XPMjRnPWToE2Cv z3F|nEbA7n5`ee%1xsz>NXai>7LFb9t*iT(jp>5Oref=q-QLOY9be~?dQYM>*l(8UN zXhxgj&i;CM!To%$y(|!&!k{m7ss}_l{(O_U@d% zo`C+_7oStXTu@;=|DGadxSBxl?OWMJ_3gqhjzZ)P%?*YMUZG2sI1ON=SpUaJ)uVE^ z5`H<6!>Agac8r12H3aeIjp*v2Ar|#)z6OuXnic{<*Fgs6o>@bKJIriPB;{a|b1{1I z+8mx`{MxjlA>78dr+A<1J*oz<$7-Hqt?yT?%^k)Ma6AYdq`27AS1mNR74^G?@p0v5^_YP(h6RU*n^~YbvsaUaGj!WgJ7#PG>x_Gi4+0kb zh-Z(YK*Cv1Wag22TXB3iGLSgf&h_)VG196B{t4qpb*S7ADD$^cp zv(jT869e|Bkv3XIE~axT>oM-#xg}1mfNA8K#If!L_DuK0I|RHxMSw3(#pyELRqD5q z6de5c2sZY4z&;%l{pRFl?)xFLZ?v<5N{GhR);Ofvto07ApsDnCYnigLiBUlQMzJ#< zeSWQHnC8Bm`yp>?6ujEPF#ROQ{O2dErFw3w!ZutN0ch(1pj6Z%ki4Df@ z0XH4ix|t~|5+9&VR3me;iYMj zA+MoS_z9}tZsH$L9<@mY)cu$Y=#l}ETyZfz6pNckrctQwN|vm05@eq~H)4YJGuht1aJ@gDxmF5G2#&%c`j;C(7w^zm-(f155GpJ$ zO731zKBK%+6$s%b3j)7z2s8g>C^2}mKI|8L-JR>FL+~v#8Y?sTI0}C98GYDvI;sgX z`XkOCzlr)rF+kSu5NAPB`lqxe=A(`7?rjdw*fs8sO-nNI{9;1Ni;+!^ZzuoZLWN1E z=-Nrl>8DE!r{0sp%$gf~$R=ZZB^gsDGU)$2yrI=G(41#{d;9CFRltl&`2}wWZ%gzH z!6%GpcAN*5IsD~KSha^D9J#&Oq{G?+uWS5`^_nn;sfh2JR$mKI+0DB?XPIEXDA@ac z@>CZKgf(<+zGZ%KP7(Eo3YUaCkUM*q>#ui>ozU>^3+z9}zx|UhoLE#FRE`}PIJD3F z!YMMZqtx(~rRBaXMGgEV7maR>T-2kCWY?tUxYpg1AL(?8xs^JCsw#FnJ?UfAEd%0l z&|)>{L>e&{J|~$2W#`@+ICDFRu_t-C7S3 z_cy5Xf%k-0x?Q(G_D8^ETyA^6HsV0Ir>phB$S;Qjry}Hf|3?#J7yyoqb&Nr5frD01?1*}po~z5$diD{YWXLA^(_NI>jE4ZrM``T=0v!jOL6XXp&lHK(tsevuQqio@N(%u6dpk$6qWGyHb|3|Gq0h}dWEGtYUEcNd5zN+a{*Sdgd@*B}J377Sp41bh^K_G`^tFHlPH~X>PW`Q z*{{e2QzdmbuTLWDh)&U+(0m2klJWMT{Vl)+c}t)6h2jxD`Dt^tOApm6MW1!L1>b#G zxwP3+0w^WDQ(NAB;D~qz0{A-2d*zlm-$T#Jl0oT;FwYzGN3?8?Syj*P6CUCAPD>hB z#B;kF-!6_F;eNZscqI<8=g6o)FoL2bh<)3LYZWo18>m#$9&98wCd#=kz|~ocRxFv6 zyDFxX&hkVQmHqxOksY;Vp(aJkjo2EQz!i0zK30)qjwjka;Zy#tNs8@t#L)?^l`U&y TBm0Eo9~F}x904oQw0izOh8C+m literal 3262 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 J%J6c`KLBMdLBIe2 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0021.png b/cinema/gba/window/zmc-window-mosaic/baseline_0021.png index 792199be69a35dc73b4c30fefca36331738ff7e0..d47eb372faa1ee2d7b7eda648f99588ca3b2b250 100644 GIT binary patch literal 3284 zcmZ`+c{mhW8@Hq+$r4#6X-X6~OJmH~QVp_z~w=hCN zV`*$twjq;k?8}JE6uzl@zx#am`=0NQbI$WS%kO>9?>*-|&q**hH8{*C$;ZLLaoF%G z%#z)%vCC6lE_U5mAEe5`!JlggyJUSoZyDF(J$Y3u-a%j6zW9t3m10S??Nl&c;+*9I z=r24s+ii_V6m}O-k*kxriL7|<96X8_4P7pDvkjBK%Rg+*DG=4QY9)Y|PrSEHly43_ z?;xIdSqp2Fam(PT1h$tM*a5m1Qb~N=yB&xbDS+_T2byH;MZ{)kgr15+G8WO{JsW5C zCN`sWBRL>aGN4pG-owwhE_0VX=+iy-7o3ED5*bf9G15Yt#cvwt98ZZnZhf3O-tG%| zt|jJP>OxJLQhuTFn#}GfTdrI1=b*Ri&A&7HHf2C;w$qw(XpWmYS6cPb4??LKW4cb72m1z8Kvzt zoQuULKM8<{zibsX2v$0=N0fK){Z@8cC0C(G)H7^_!Tq2QfXY?aF_nY6V`ViP`vwAm z#1jG&WIFJ&_aW+GFR$m3@IUU#3B^XYe4eQ%!$7;Q-e~cYx`X&8Cj4`vMNS(D0~dld znv-y$-cp4UY>kqFW*zH*9-@gyl4kTg{B4_GWGa{yCn0N%kZgmEsm?LdalWXrDrU%= z$t8`;1{Q4sirqk<@lf&UgrYgSjN-*XC`d?%ZJOZ(EDH1X?F)`?J*l}ApuybmsG{G^ z1!jcJd_|q-Ni{$W?dZZRKoZFhC2?%hh9M~?s%owEfciyy5$!}EURCpDfHr)7rN`sd z(z3U;^o)E@ehz78v4ji@SWP4HDhtH7nwGZySP5t_?pM(F@EHXucAqSAtT0UpZ>FcN zJYRAYb`B2CUp$}Z1^W7GJ@@c{$s$O$WvzqLcaG05w*@M!C*tbAY}%lm6;KqD?ye6b z784~y@(o~kN|4F#{WtoFkjqKUhy0*#(=o_L`j?LKEET#y8|@n)RcDGE#VfPWG(?-H zz{3wmI*!O6R2?Xz3kty>_K)M#`GH_zk8=C)3jD%k|Ds=E&U&&M%WK_2E?04v℘F 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 mhvaLQepugp(EkBbs~|`K diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0022.png b/cinema/gba/window/zmc-window-mosaic/baseline_0022.png index 08a6d3dbae13d1b468c57d8edb4537703951d2cc..c9039b6aec542159e77a256ad450a1d8bee0ebec 100644 GIT binary patch delta 3118 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 IM6N<$f*!0KS^xk5 delta 2962 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 IM6N<$f-FM4S^xk5 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0023.png b/cinema/gba/window/zmc-window-mosaic/baseline_0023.png index 57d99fa3c21aa9a9dfa08c756b0efe261dd00320..382444fb8201fbc089060727fb0a1bc507e01d30 100644 GIT binary patch delta 2987 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 literal 2903 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 diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0024.png b/cinema/gba/window/zmc-window-mosaic/baseline_0024.png index a49291156c38d6d78b152c3373bd7c664d4167a6..53d25bf03724cdcca056f974127e42a1bf4bb57e 100644 GIT binary patch delta 2889 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 literal 2721 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 iE4W9p?^~bDe|TU#2H}=H zt4b|lq}EZiwx(!Q5E^Q!tyP+)A+9?!-*@lBJx}N1eCK@UJp89kd0Z(=i*&fi-uC>p zNZNXFcSg0L@|}IGY@lq~9hcet6{e;aXbgnjpr@w9A3B*xSlkbx4vu?*cj3+)8-bg( z1M0;p-r8fMmEU~SZH9N!p6{K4H6G@mU*6vB2~sV2)x$$;ETomOY>z4zK1@I;xm>}} zrKK-jJ70G9c!o3d+DJt@i6g{6d$ih6{bjNvGrRKZyDW4** z`>uRN`mLX<`TjT0iSeC?+!kDONP~_e4(Kae17mXrkt(mmF7A|(loCrqohJetYpQ=3 z7;HG1`r13VcFXK7SwwMK2IsZE1_Putq~^D2tYunr?U#;HTm+#u;NAVU*i%q$?q$Yj zeW(oJ28uf{Ri&WqCeZ$+wB?0;P_K_(D@!J>Lel5;OXF9njZT>DV#{Ik8S&s`;1x5s zm`tJF8@Y;|l~s3=>zgi)EzFfGe#7M~dWZb}#;yHM2*c1#U`n!CW=2?6_gg=gNA!b7 z@4!^zVJV8R5%Y7h-j4)4R zg-Kw&BGo)X&eZ|w{ow3@KrP9@%~+313SSvr+AA}+-&E+JJkSFm`|_od%F>tWSEx-f9Q||5;|D~tS&y%LYb`Jw@rY> zi)kWkNm5c`Fmd3cy6?Q#}n+7ybETMVBHh$yg zB0}hRPa!1Yu@KqG7lo<9G+!_~uzh7!x+L3C#}#oD#>mdh)?yY7VdCd#v8E}@v4E^v zUK>eYKd2Eucm1jope9+282M{}<;PcYx27XRl?uhY(Dl*|NT%uDX^m{&8FZs~^${*v zxoRx;d%zM10S$-h#adAq8nQUxg-&e~?pxy0UMR0?(bjq5#@VLUn7|2lhlYD3TKI_g zF;6nccfHWtZl=p)PUQ8pud6jDE|f_tjrjK9^0SXn?l)8hs_5=%3geiugqWDFxS~dv zS%hUtQu=g|af|2p=qVGuzsq-)95B4uj(FiOeT{k&#g!F9ZTbJV>=3R0!ZpG`?sXuS`Fg8Y)9VO24@qN0P9n)DNAB zyIl4XaxCp39}Vz*vO3jDFZRwuzaO7h-c;JP2=`Wxo@n2Ukzk8Ae3^riZ7B7FAJ_Ug zM;V85`N(!}c`|C<$;dCv-h%Kh%1pViP2+BiB?PGBXwUPGUSDt# zuy1}*x$D5T-yKjn!hbhzf|5+ut-k*kZix3tzdN~cu5Q!*;y=N?G&@zFapnw7JSs!p z(NwgkaZ-AuzX^m0F9gRBhJ4B-e~il5T-K_wn+0c%k{?gnfY@sReT)Kp8ppr>kbwF< zN5x^xj&J+*rw_G@biGDu%a!FIh&#`{Uzo@t+2_1Me)=B_D_kM;2ZW4}RJ_ zZCvABzhNpkA$l>UW8@53v=m&RrM0}2pe_vV+`?JV75;Wx|IB~)j+UDk9GOq||I2i@ zKYrZ%ySo~21)B69@0T-uvqZ)jY%@RKH(>F2$I38Pb2&)wHDjwkM`d{Y#Dq*qV|nkA z5zy}vmh!Z+-qqdN>7U~MtFy5^c}0rm5^lBgJ7=#f!&xiP+lYYq=rXp@rp$=Y-qdz= zT9Lcfdbr$4y)t-Ov(-0=nkfk}u(BvB-PzuS-@<#MQa{fW?h@pX^=p&mQP}3Kc8UH; zeO^|dI)u}m9k7bRbIMDI$loJy z(8!#}zgN->Mqm7fIpf@UG>+|GV;nMHDA*Cxpr~miG+N)($ zjd96nnZq!93Zd9&r6`c7Lo-|8V`0F-AS?UD@{O9Ym)S0(U9=_&PPqg<1YPK zKy;tjpO}Qis1k0VY^RwZ7-!?jK|fP^?jo77hod^D{g=MbY^O1A`uHgiQ1sTIYi?pVhjCN9K2!%-y5Xmm2O} z+dyFQpSGS%#Xq12FO6=Y^tYXz;H9^p6;|tiXhuR2uP!EBjB0HnKB#111r@J;wPj1{Dp diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0027.png b/cinema/gba/window/zmc-window-mosaic/baseline_0027.png index b9f6dc6f24cd93444ceb121dbba41c9dca4a2ad2..d6fcf669405fe236d93c9e7590725801031a180b 100644 GIT binary patch delta 2182 zcmV;12zmGY5xEkOBU%XnNkl!H%mi3`PAALKf;G|NoU)RLF`DdW!%J6xx`E z8S1%5vtZH`GUbdLjDdd*DQFs1cLgD6hH*P>7@E#!4MO>8sCuh*;9WHg4XPce8bzfX zk$xI~_g-k7iwm9i^K##Hb=#h?T(@ysUF+IuySvMEJr29?`%=rVJ|yrx{M*cMx6YCG z<1c?>IXaCKD=FW2Vr4f%KbZIWA87~Y!Q&M7p4a_04B9PZ2n|3HxN^+--G!(Z5u!$; z_4Yww=RHmH<_FI8U3Y4G=XLFU-xq5Kr=qlfnTVK(nH_Ve6se2du4|{A>%1qTwTsct z678Muh-mHZetWcV?dZ{V@H2?=hlo^8zv}mJda^n4aB;Hp^Y&=%wB7gZsf7x6b==~DTn0s7a2P;GZAqN*dn+3&3t-S=*>Jd5v8fF)m9^`9CP0Iwe`@7&Szp! zXy+UeiHL|4bUFlL=VX~#a*00Gka({v_(JZc@g zIp#P$nX}iK-}|~e^otWEeb=R0!{+ZLI-MxAaQfJUQkL($2V%GAFdyuB@1HjzM3Oi4 zeUr8uj|ivCUA*eMZrep!&oXBhCp}u5k>xl{!7RsOwSnC7ATF8~1Df~hyj}?898sKa z#K{Je_lrWM*MG{}6bHSXLwAXP%`+TyUdTCDRk`Vh)MhWl&dmpe%-m#$Rc9=8%{v=L*)H^^98Q zuO|a!IlwNrVTcoqN7|T(h=j%?!pz$Ylv+;?h|u62%<^0?bRan~QEELefzaR;w_&(f z)uwi;uv%(8Z-LO@4NFWA(K!C7Zrg#^or9SD{>mD zyBMe*ZgLqEe<321PX{A^z^U(3s4^aVI-Gnl(%%F^7bZvLv?sJK3xqC4zRJ(?m zV)I1d`(ph;AoNERwK)Q(KLSDnQ$XnBoDiNP2-3%a(7+TB`XESuv|SVI6<`VoeFTK( z%Yq#POaY;RDIhd31*+{9s0tEFKfnnT07739Rgn_@uM#lj0H)ZD(3_^$Rdu+$@V8LS zB5rxLrtwn~RczGtRYl#BPs^@%GLmoiyRITsg=QBbLIYDk=p*#MJpix=ga)R7&<8<~ zz9tYFm;yo{XBZHFrY{GC2Bv_}AAul!H6Zk-!0Af?p$h<~uLFcG1Dw7H5V{z{&_nBA z2j!!MiB4B7KG@#@pZh5c-BYM;sH(F+?O!Xl`+EKG@U?!1eJ z!5;zLTm8z9GP-t<_*rf z?iGMN>Wow!*rqw*JLteOnUwp_`0mC*Auz=ep)&&$EauSQ=Q(-dD416+-d(smFR zTn)^jfhiy~Fa?ALrug@b_Yb&z-;lu%fs>5}CzEam3lgGM-B9$Vx#%jWZcQ6d5()}{ zlZ^%+f6CBTmvHG4g8&9#=J%b>2Tzg(r*L21Ygtt^@d*$bd_-SWb8N2LmWV`zMBK8Q z58-E=hS1<6>S5@+j)>~EjSCdQYVxsq&T|^PQ_HpO+vCX{_?5V8ZZN5O9zFPw{>kPQ z_Yjzf9TAaux=)c?+rCGwy8Ec|No|TXtGSUBqu_f1rI6XxpE`lw-j?-19r%zVK-t zZ0F)VC36fW@;iyP*fH9f9^q-*pMlVz7*XXpg!&zuZ%s27ex!D4+c)`6p^Ijg7s)ro z+@su@CY}M2q-C%3o|oG8jh>nPOrlJygE#zG!Nv=4PzX%%A4&3_cv!7u)Bpeg07*qo IM6N<$f}cAxy#N3J delta 2099 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 diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index e8a474c0a..a8a471e09 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -17,19 +17,12 @@ #define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ SPRITE_YBASE_ ## DEPTH(inY); \ unsigned tileData; \ - if (outX % mosaicH) { \ - if (!inX && xOffset > 0) { \ - inX = mosaicH - (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } else if (inX == width - xOffset) { \ - inX = mosaicH + (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } \ - } \ for (; outX < condition; ++outX, inX += xOffset) { \ int localX = inX - xOffset * (outX % mosaicH); \ - if (localX < 0 || localX > width - 1) { \ - continue; \ + if (localX < 0) { \ + localX = 0; \ + } else if (localX > width - 1) {\ + localX = width - 1; \ } \ SPRITE_XBASE_ ## DEPTH(localX); \ SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 0e5d7dd5f..4c83844ad 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -805,11 +805,17 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) { struct GBAVideoRendererSprite* sprite = &renderer->sprites[i]; int localY = y; renderer->end = 0; + if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { + continue; + } if (GBAObjAttributesAIsMosaic(sprite->obj.a)) { localY = mosaicY; - } - if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) { - continue; + if (localY < sprite->y) { + localY = sprite->y; + } + if (localY >= sprite->endY) { + localY = sprite->endY - 1; + } } for (w = 0; w < renderer->nWindows; ++w) { if (renderer->spriteCyclesRemaining <= 0) { From 1868ccaadd9e50ad929a72666956f6b01e2e219c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 17:28:34 -0700 Subject: [PATCH 37/63] Qt: Only close GL context on thread when shutting down --- src/platform/qt/DisplayGL.cpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index dc360cdb0..3bba105e3 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -315,18 +315,8 @@ void PainterGL::resizeContext() { return; } - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } - QSize size = m_context->screenDimensions(); m_backend->setDimensions(m_backend, size.width(), size.height()); - if (!m_active) { - m_gl->doneCurrent(); - } } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { @@ -493,12 +483,6 @@ void PainterGL::setShaders(struct VDir* dir) { return; } #ifdef BUILD_GLES2 - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } if (m_shader.passes) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); @@ -507,9 +491,6 @@ void PainterGL::setShaders(struct VDir* dir) { if (m_started) { mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); } - if (!m_active) { - m_gl->doneCurrent(); - } #endif } @@ -518,19 +499,10 @@ void PainterGL::clearShaders() { return; } #ifdef BUILD_GLES2 - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } if (m_shader.passes) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); } - if (!m_active) { - m_gl->doneCurrent(); - } #endif } From 838d13f2efb8b97aa7e09f93441b5f5090326b8f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 18:19:55 -0700 Subject: [PATCH 38/63] GBA Video: Improve GL sprite mosaic --- src/gba/renderers/gl.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index e7941f161..ff77ed4e7 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -404,10 +404,13 @@ static const char* const _renderObj = "void main() {\n" " vec2 incoord = texCoord;\n" " if (mosaic.x > 1) {\n" - " incoord.x -= mod(mosaic.z + incoord.x, mosaic.x);\n" + " incoord.x = int(clamp(incoord.x - mod(mosaic.z + incoord.x, mosaic.x), 0, dims.z - 1));\n" + " } else if (mosaic.x < -1) {\n" + " incoord.x = dims.z - incoord.x - 1;" + " incoord.x = clamp(dims.z - int(incoord.x - mod(mosaic.z + incoord.x, -mosaic.x)) - 1, 0, dims.z - 1);\n" " }\n" " if (mosaic.y > 1) {\n" - " incoord.y -= mod(mosaic.w + incoord.y, mosaic.y);\n" + " incoord.y = int(clamp(incoord.y - mod(mosaic.w + incoord.y, mosaic.y), 0, dims.w - 1));\n" " }\n" " ivec2 coord = ivec2(transform * (incoord - dims.zw / 2) + dims.xy / 2);\n" " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" @@ -1340,7 +1343,11 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } if (GBAObjAttributesAIsMosaic(sprite->a)) { - glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], GBAMosaicControlGetObjV(renderer->mosaic) + 1, GBAMosaicControlGetObjH(renderer->mosaic) + 1, x, spriteY); + int mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + mosaicH = -mosaicH; + } + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], mosaicH, GBAMosaicControlGetObjV(renderer->mosaic) + 1, x, spriteY); } else { glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], 0, 0, 0, 0); } From bdb584a39849899db5062f0894058c7783860381 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 19:12:45 -0700 Subject: [PATCH 39/63] Qt: Fix GL scaling --- src/platform/qt/CoreController.cpp | 1 + src/platform/qt/CoreController.h | 1 + src/platform/qt/DisplayGL.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index bd12de40d..d073ac654 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -86,6 +86,7 @@ CoreController::CoreController(mCore* core, QObject* parent) context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer->data()), controller->screenDimensions().width()); } + QMetaObject::invokeMethod(controller, "didReset"); controller->finishFrame(); }; diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index e31602117..59fd37ee5 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -170,6 +170,7 @@ signals: void crashed(const QString& errorMessage); void failed(); void frameAvailable(); + void didReset(); void stateLoaded(); void rewound(); diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 3bba105e3..4bbb5a575 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -108,6 +108,7 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); + connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext); } void DisplayGL::stopDrawing() { From fd6948da4d5c066ecbb298081ed5a8cb661c9c86 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 19:16:07 -0700 Subject: [PATCH 40/63] GBA Video: OBJWIN sprites cannot be mosaic --- src/gba/renderers/gl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index ff77ed4e7..6d7aafd14 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -1342,7 +1342,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } - if (GBAObjAttributesAIsMosaic(sprite->a)) { + if (GBAObjAttributesAIsMosaic(sprite->a) && GBAObjAttributesAGetMode(sprite->a) != OBJ_MODE_OBJWIN) { int mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; if (GBAObjAttributesBIsHFlip(sprite->b)) { mosaicH = -mosaicH; From 35200b6c7ae59841b429dbd196d7a2f9661df88f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 May 2019 23:57:48 -0700 Subject: [PATCH 41/63] GB: Fix HALT when IE and IF unused bits are set (fixes #1349) --- CHANGES | 1 + src/gb/gb.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fa64821a2..60e0bd8b9 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Emulation fixes: - GBA: Fix RTC on non-standard sized ROMs (fixes mgba.io/i/1400) - GBA Memory: Prevent writing to mirrored BG VRAM (fixes mgba.io/i/743) - GBA Video: Fix sprite mosaic clamping (fixes mgba.io/i/1008) + - GB: Fix HALT when IE and IF unused bits are set (fixes mgba.io/i/1349) Other fixes: - Qt: More app metadata fixes - Qt: Fix load recent from archive (fixes mgba.io/i/1325) diff --git a/src/gb/gb.c b/src/gb/gb.c index fc3c174b0..5f5178a90 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -723,7 +723,7 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle void GBHalt(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - if (!(gb->memory.ie & gb->memory.io[REG_IF])) { + if (!(gb->memory.ie & gb->memory.io[REG_IF] & 0x1F)) { cpu->cycles = cpu->nextEvent; cpu->halted = true; } else if (gb->model < GB_MODEL_CGB) { From e90b8d235467679229ab16032c64bf8508bb5440 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 07:14:03 -0700 Subject: [PATCH 42/63] Qt: Fix crash when GL context is missing --- src/platform/qt/Window.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 335149cf3..24a37b0d8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -634,6 +634,8 @@ void Window::closeEvent(QCloseEvent* event) { if (m_controller) { event->ignore(); m_pendingClose = true; + } else { + m_display.reset(); } } From 18f27d5ee6d4d1d74a9a9826c34aab6cbe8219dc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 07:17:42 -0700 Subject: [PATCH 43/63] Qt: Fix logging trying to open a null file --- src/platform/qt/LogController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index e6f80e0b1..dfd472c2f 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -127,6 +127,9 @@ void LogController::logToStdout(bool log) { void LogController::setLogFile(const QString& file) { m_logStream.reset(); + if (file.isEmpty()) { + return; + } m_logFile = std::make_unique(file); m_logFile->open(QIODevice::Append | QIODevice::Text); m_logStream = std::make_unique(m_logFile.get()); From 3aae19a8073a60587a908e1029a253cc42b7b9d3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 17:17:24 -0700 Subject: [PATCH 44/63] Qt: Windows fixes --- src/platform/qt/DisplayGL.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 4bbb5a575..e0cf146a9 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -35,6 +35,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) , m_gl(nullptr) { setAttribute(Qt::WA_NativeWindow); + windowHandle()->create(); // This can spontaneously re-enter into this->resizeEvent before creation is done, so we // need to make sure it's initialized to nullptr before we assign the new object to it @@ -263,6 +264,10 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent if (!painter->m_gl->isValid()) { return; } + painter->m_gl->makeCurrent(painter->m_surface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif painter->m_gl->swapBuffers(painter->m_gl->surface()); }; From 84c010783c18fcb53ffddc83ba818055c90da758 Mon Sep 17 00:00:00 2001 From: Cameron Cawley Date: Tue, 21 May 2019 21:24:03 +0100 Subject: [PATCH 45/63] SDL: Simplify setting full screen mode with SDL1 --- src/platform/sdl/gl-common.c | 4 ++-- src/platform/sdl/main.c | 4 ---- src/platform/sdl/main.h | 2 -- src/platform/sdl/sdl-events.h | 2 +- src/platform/sdl/sw-sdl1.c | 4 ++-- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index e4d400b29..d9c62c9a5 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -51,9 +51,9 @@ void mSDLGLCommonInit(struct mSDLRenderer* renderer) { #else SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); #ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #endif SDL_WM_SetCaption(projectName, ""); #endif diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 21e4459b7..d82d4fb24 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -131,12 +131,8 @@ int main(int argc, char** argv) { renderer.viewportWidth = renderer.core->opts.width; renderer.viewportHeight = renderer.core->opts.height; -#if SDL_VERSION_ATLEAST(2, 0, 0) renderer.player.fullscreen = renderer.core->opts.fullscreen; renderer.player.windowUpdated = 0; -#else - renderer.fullscreen = renderer.core->opts.fullscreen; -#endif renderer.lockAspectRatio = renderer.core->opts.lockAspectRatio; renderer.lockIntegerScaling = renderer.core->opts.lockIntegerScaling; diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index bd4f2cecc..5f286d5fd 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -54,8 +54,6 @@ struct mSDLRenderer { SDL_Texture* sdlTex; SDL_Renderer* sdlRenderer; SDL_GLContext* glCtx; -#else - bool fullscreen; #endif unsigned width; diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 7df0672c3..8cb1aede2 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -63,10 +63,10 @@ struct mSDLPlayer { size_t playerId; struct mInputMap* bindings; struct SDL_JoystickCombo* joystick; + int fullscreen; int windowUpdated; #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; - int fullscreen; struct mSDLRumble { struct mRumble d; diff --git a/src/platform/sdl/sw-sdl1.c b/src/platform/sdl/sw-sdl1.c index c8002fd24..90a9d2499 100644 --- a/src/platform/sdl/sw-sdl1.c +++ b/src/platform/sdl/sw-sdl1.c @@ -22,9 +22,9 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) { bool mSDLSWInit(struct mSDLRenderer* renderer) { #ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #endif SDL_WM_SetCaption(projectName, ""); From 57a7a0a2911aaf28241efce70b12fc9add585dc3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 14:26:30 -0700 Subject: [PATCH 46/63] GBA Video: Fix rounding issues on Nvidia --- src/gba/renderers/gl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 6d7aafd14..ab57298c3 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -404,13 +404,13 @@ static const char* const _renderObj = "void main() {\n" " vec2 incoord = texCoord;\n" " if (mosaic.x > 1) {\n" - " incoord.x = int(clamp(incoord.x - mod(mosaic.z + incoord.x, mosaic.x), 0, dims.z - 1));\n" + " incoord.x = clamp(incoord.x - int(mod(mosaic.z + incoord.x, mosaic.x)), 0, dims.z - 1);\n" " } else if (mosaic.x < -1) {\n" " incoord.x = dims.z - incoord.x - 1;" - " incoord.x = clamp(dims.z - int(incoord.x - mod(mosaic.z + incoord.x, -mosaic.x)) - 1, 0, dims.z - 1);\n" + " incoord.x = clamp(dims.z - incoord.x + int(mod(mosaic.z + incoord.x, -mosaic.x)) - 1, 0, dims.z - 1);\n" " }\n" " if (mosaic.y > 1) {\n" - " incoord.y = int(clamp(incoord.y - mod(mosaic.w + incoord.y, mosaic.y), 0, dims.w - 1));\n" + " incoord.y = clamp(incoord.y - int(mod(mosaic.w + incoord.y, mosaic.y)), 0, dims.w - 1);\n" " }\n" " ivec2 coord = ivec2(transform * (incoord - dims.zw / 2) + dims.xy / 2);\n" " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" From 6c1bc5548cf61da25f1fc70fe961fbbadff2b714 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 15:17:42 -0700 Subject: [PATCH 47/63] GBA Video: Fix scale/rot sprite mosaic in GL --- src/gba/renderers/gl.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index ab57298c3..6546dc57d 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -404,13 +404,15 @@ static const char* const _renderObj = "void main() {\n" " vec2 incoord = texCoord;\n" " if (mosaic.x > 1) {\n" - " incoord.x = clamp(incoord.x - int(mod(mosaic.z + incoord.x, mosaic.x)), 0, dims.z - 1);\n" + " int x = int(incoord.x);\n" + " incoord.x = clamp(x - int(mod(mosaic.z + x, mosaic.x)), 0, dims.z - 1);\n" " } else if (mosaic.x < -1) {\n" - " incoord.x = dims.z - incoord.x - 1;" - " incoord.x = clamp(dims.z - incoord.x + int(mod(mosaic.z + incoord.x, -mosaic.x)) - 1, 0, dims.z - 1);\n" + " int x = dims.z - int(incoord.x) - 1;\n" + " incoord.x = clamp(dims.z - x + int(mod(mosaic.z + x, -mosaic.x)) - 1, 0, dims.z - 1);\n" " }\n" " if (mosaic.y > 1) {\n" - " incoord.y = clamp(incoord.y - int(mod(mosaic.w + incoord.y, mosaic.y)), 0, dims.w - 1);\n" + " int y = int(incoord.y);\n" + " incoord.y = clamp(y - int(mod(mosaic.w + y, mosaic.y)), 0, dims.w - 1);\n" " }\n" " ivec2 coord = ivec2(transform * (incoord - dims.zw / 2) + dims.xy / 2);\n" " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" From 2781a2a9f951b39ff89e47d0d13106918e1316c3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 15:32:20 -0700 Subject: [PATCH 48/63] GBA Video: Implement sprite mosaic on transformed sprites --- CHANGES | 1 + README.md | 1 - README_DE.md | 1 - src/gba/renderers/software-obj.c | 45 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 60e0bd8b9..eff7ec3f7 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Emulation fixes: - GBA Memory: Prevent writing to mirrored BG VRAM (fixes mgba.io/i/743) - GBA Video: Fix sprite mosaic clamping (fixes mgba.io/i/1008) - GB: Fix HALT when IE and IF unused bits are set (fixes mgba.io/i/1349) + - GBA Video: Implement mosaic on transformed sprites (fixes mgba.io/b/9) Other fixes: - Qt: More app metadata fixes - Qt: Fix load recent from archive (fixes mgba.io/i/1325) diff --git a/README.md b/README.md index b3350460a..9042c0ead 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,6 @@ Footnotes [1] Currently missing features are - OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) -- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) [2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. diff --git a/README_DE.md b/README_DE.md index a41d5fbcd..172b5f826 100644 --- a/README_DE.md +++ b/README_DE.md @@ -215,7 +215,6 @@ Fußnoten [1] Zurzeit fehlende Features sind - OBJ-Fenster für die Modi 3, 4 und 5 ([Bug #5](http://mgba.io/b/5)) -- Mosaik-Effekt für umgewandelte OBJs ([Bug #9](http://mgba.io/b/9)) [2] In manchen Fällen ist es nicht möglich, die Größe des Flash-Speichers automatisch zu ermitteln. Diese kann dann zur Laufzeit konfiguriert werden, es wird jedoch empfohlen, den Fehler zu melden. diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index a8a471e09..de056596d 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -48,6 +48,31 @@ SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ } +#define SPRITE_TRANSFORMED_MOSAIC_LOOP(DEPTH, TYPE) \ + unsigned tileData; \ + unsigned widthMask = ~(width - 1); \ + unsigned heightMask = ~(height - 1); \ + int localX = xAccum >> 8; \ + int localY = yAccum >> 8; \ + for (; outX < condition; ++outX, ++inX) { \ + renderer->spriteCyclesRemaining -= 2; \ + xAccum += mat.a; \ + yAccum += mat.c; \ + \ + if (outX % mosaicH == 0) { \ + localX = xAccum >> 8; \ + localY = yAccum >> 8; \ + } \ + \ + if (localX & widthMask || localY & heightMask) { \ + continue; \ + } \ + \ + SPRITE_YBASE_ ## DEPTH(localY); \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + #define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); #define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4; @@ -173,6 +198,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (end < condition) { condition = end; } + int mosaicH = 1; + if (GBAObjAttributesAIsMosaic(sprite->a)) { + mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (condition % mosaicH) { + condition += mosaicH - (condition % mosaicH); + } + } int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7); int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7); @@ -223,6 +255,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else if (mosaicH > 1) { + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN); + } else { + SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL); + } } else if (objwinSlowPath) { objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN); @@ -232,6 +271,12 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } else { if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else if (mosaicH > 1) { + if (objwinSlowPath) { + SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN); + } else { + SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL); + } } else if (objwinSlowPath) { SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN); } else { From 787c99b949b93e7263c53f483ff556dbd9684869 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 May 2019 22:32:24 -0700 Subject: [PATCH 49/63] Qt: Improve GL syncing --- src/platform/qt/CoreController.h | 3 +++ src/platform/qt/Display.cpp | 2 +- src/platform/qt/DisplayGL.cpp | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 59fd37ee5..978421249 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -98,6 +98,9 @@ public: void setInputController(InputController*); void setLogger(LogController*); + bool audioSync() const { return m_audioSync; } + bool videoSync() const { return m_videoSync; } + public slots: void start(); void stop(); diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index ee754c4f0..ed7c1fd38 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -20,7 +20,7 @@ Display* Display::create(QWidget* parent) { #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) QSurfaceFormat format; format.setSwapInterval(1); - format.setSwapBehavior(QSurfaceFormat::TripleBuffer); + format.setSwapBehavior(QSurfaceFormat::SingleBuffer); #endif switch (s_driver) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index e0cf146a9..77439874a 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -264,11 +264,12 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent if (!painter->m_gl->isValid()) { return; } + painter->m_gl->doneCurrent(); + painter->m_gl->swapBuffers(painter->m_surface); painter->m_gl->makeCurrent(painter->m_surface); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif - painter->m_gl->swapBuffers(painter->m_gl->surface()); }; m_backend->init(m_backend, 0); @@ -383,7 +384,7 @@ void PainterGL::draw() { m_backend->swap(m_backend); if (!m_delayTimer.isValid()) { m_delayTimer.start(); - } else if (m_gl->format().swapInterval()) { + } else if (m_gl->format().swapInterval() && m_context->videoSync()) { while (m_delayTimer.elapsed() < 15) { QThread::usleep(100); } From 52ac4d6868f4f61bf62f3d279197a83786fb4ff9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 11:01:16 -0700 Subject: [PATCH 50/63] GBA Audio: Ensure mixer is initialized --- src/gba/audio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/audio.c b/src/gba/audio.c index b337043a3..d2650d6e8 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -55,6 +55,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { audio->forceDisableChA = false; audio->forceDisableChB = false; audio->masterVolume = GBA_AUDIO_VOLUME_MAX; + audio->mixer = NULL; } void GBAAudioReset(struct GBAAudio* audio) { From a4ceefa3218e7dda4aea2c33f72e257598b7bb4d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 11:10:55 -0700 Subject: [PATCH 51/63] Qt: Fix exclusive options unchecking --- src/platform/qt/Action.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/Action.cpp b/src/platform/qt/Action.cpp index 0fe4be787..e5d6600d4 100644 --- a/src/platform/qt/Action.cpp +++ b/src/platform/qt/Action.cpp @@ -67,6 +67,10 @@ void Action::trigger(bool active) { return; } + if (m_exclusive && !m_booleanFunction) { + active = true; + } + if (m_function && active) { m_function(); } From 7f86ebc4e6f8f1d24f2a6276a65b0f2635aad81c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 11:15:21 -0700 Subject: [PATCH 52/63] Qt: Preserve native FPS target when saving settings --- src/platform/qt/SettingsView.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index d5d0a91ac..9acf56a99 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -370,7 +370,6 @@ void SettingsView::updateConfig() { saveSetting("videoSync", m_ui.videoSync); saveSetting("audioSync", m_ui.audioSync); saveSetting("frameskip", m_ui.frameskip); - saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("autofireThreshold", m_ui.autofireThreshold); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); @@ -409,6 +408,13 @@ void SettingsView::updateConfig() { saveSetting("fastForwardRatio", m_ui.fastForwardRatio); } + double nativeFps = double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH); + if (nativeFps - m_ui.fpsTarget->value() < 0.0001) { + m_controller->setOption("fpsTarget", QVariant(nativeFps)); + } else { + saveSetting("fpsTarget", m_ui.fpsTarget); + } + switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) { case IDLE_LOOP_IGNORE: saveSetting("idleOptimization", "ignore"); From 1084246e8ff7fa66c540c273e40b9cb857fd79e7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 11:18:22 -0700 Subject: [PATCH 53/63] CHANGES: Update --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index eff7ec3f7..2d1a66b0e 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,7 @@ Other fixes: - Core: Improved lockstep driver reliability (Le Hoang Quyen) - GBA: Fix skipping BIOS on irregularly sized ROMs - Qt: Fix bounded fast forward with Qt Multimedia + - Qt: Fix saving settings with native FPS target Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash From eb5a7951010f00548007b549f08d72d2bc3d4940 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 11:29:24 -0700 Subject: [PATCH 54/63] Qt: Add native FPS button to settings view --- CHANGES | 1 + src/platform/qt/SettingsView.cpp | 4 ++++ src/platform/qt/SettingsView.ui | 23 +++++++++++++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 2d1a66b0e..f5c0f18e2 100644 --- a/CHANGES +++ b/CHANGES @@ -69,6 +69,7 @@ Misc: - GBA Video: Clean up dead code in sprite rendering loop - FFmpeg: Support audio-only recording - Qt: Increase maximum magnifications and scaling + - Qt: Add native FPS button to settings view 0.7.1: (2019-02-24) Bugfixes: diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 9acf56a99..d8bb7e3e7 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -57,6 +57,10 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC } }); + connect(m_ui.nativeGB, &QAbstractButton::pressed, [this]() { + m_ui.fpsTarget->setValue(double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH)); + }); + if (m_ui.savegamePath->text().isEmpty()) { m_ui.savegameSameDir->setChecked(true); } diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 1a7a9e83f..1f51e5abf 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -6,8 +6,8 @@ 0 0 - 790 - 686 + 849 + 753 @@ -399,21 +399,21 @@ - + Qt::Horizontal - + Sync: - + @@ -431,27 +431,34 @@ - + Lock aspect ratio - + Force integer scaling - + Bilinear filtering + + + + Native (59.7275) + + + From a0af0ce1419eca9cc03f4b7a66638603898f66c7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 12:21:53 -0700 Subject: [PATCH 55/63] GL: Fix VAO semantics --- src/gba/renderers/gl.c | 10 ++-------- src/platform/opengl/gles2.c | 6 ++++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 6546dc57d..456fbd31e 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -571,6 +571,7 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide glBindVertexArray(shader->vao); glBindBuffer(GL_ARRAY_BUFFER, glRenderer->vbo); GLuint positionLocation = glGetAttribLocation(program, "position"); + glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL); size_t i; @@ -1131,12 +1132,12 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; _finalizeLayers(glRenderer); + glBindVertexArray(0); glRenderer->firstAffine = -1; glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx; glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy; glRenderer->bg[3].affine[0].sx = glRenderer->bg[3].refx; glRenderer->bg[3].affine[0].sy = glRenderer->bg[3].refy; - glFlush(); } void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -1273,7 +1274,6 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 11); glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 12); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -1353,7 +1353,6 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB } else { glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], 0, 0, 0, 0); } - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1399,7 +1398,6 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1475,7 +1473,6 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1490,7 +1487,6 @@ void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], 0); glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1505,7 +1501,6 @@ void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0xA000 : 0); glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1520,7 +1515,6 @@ void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0x5000 : 0); glUniform2i(uniforms[GBA_GL_BG_SIZE], 160, 128); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index c9994f10a..168858241 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -136,9 +136,11 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { glBindVertexArray(context->initialShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glEnableVertexAttribArray(context->initialShader.positionLocation); glVertexAttribPointer(context->initialShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glBindVertexArray(context->finalShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glEnableVertexAttribArray(context->finalShader.positionLocation); glVertexAttribPointer(context->finalShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glBindVertexArray(0); @@ -303,7 +305,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { break; } } - glEnableVertexAttribArray(shader->positionLocation); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); } @@ -327,6 +328,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); + glBindVertexArray(0); } void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { @@ -463,8 +465,8 @@ void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shad glBindVertexArray(context->shaders[i].vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); - glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(context->shaders[i].positionLocation); + glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); } glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); From 572f4df7e7ed5c5bd82c791bce0c5cfd1050d516 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 13:51:42 -0700 Subject: [PATCH 56/63] GBA Video: Begin work on batching GL draw calls --- include/mgba/internal/gba/renderers/gl.h | 4 + src/gba/renderers/gl.c | 245 +++++++++++++++-------- 2 files changed, 171 insertions(+), 78 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 6b10d2777..e6f8db74c 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -150,6 +150,9 @@ struct GBAVideoGLRenderer { GLuint vramTex; unsigned vramDirty; + uint16_t shadowRegs[0x30]; + uint64_t regsDirty; + struct GBAVideoGLShader bgShader[6]; struct GBAVideoGLShader objShader[2]; struct GBAVideoGLShader finalizeShader; @@ -177,6 +180,7 @@ struct GBAVideoGLRenderer { GBAWindowControl objwin; int firstAffine; + int firstY; int scale; }; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 456fbd31e..ed4b5a28d 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -43,9 +43,11 @@ static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* ren static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y); static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y); +static void _cleanRegister(struct GBAVideoGLRenderer* renderer, int address, uint16_t value); +static void _drawScanlines(struct GBAVideoGLRenderer* renderer, int lastY); static void _finalizeLayers(struct GBAVideoGLRenderer* renderer); -#define TEST_LAYER_ENABLED(X) !renderer->disableBG[X] && glRenderer->bg[X].enabled == 4 +#define TEST_LAYER_ENABLED(X) !glRenderer->d.disableBG[X] && glRenderer->bg[X].enabled == 4 struct GBAVideoGLUniform { const char* name; @@ -761,6 +763,7 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) { glRenderer->paletteDirty = true; glRenderer->vramDirty = 0xFFFFFF; glRenderer->firstAffine = -1; + glRenderer->firstY = -1; glRenderer->dispcnt = 0; glRenderer->mosaic = 0; } @@ -793,58 +796,87 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, GBAVideoCacheWriteVideoRegister(renderer->cache, address, value); } + glRenderer->regsDirty |= 1ULL << (address >> 1); switch (address) { case REG_DISPCNT: value &= 0xFFF7; + break; + case REG_BG0CNT: + case REG_BG1CNT: + value &= 0xDFFF; + break; + case REG_BG0HOFS: + case REG_BG0VOFS: + case REG_BG1HOFS: + case REG_BG1VOFS: + case REG_BG2HOFS: + case REG_BG2VOFS: + case REG_BG3HOFS: + case REG_BG3VOFS: + value &= 0x01FF; + break; + case REG_BLDALPHA: + value &= 0x1F1F; + break; + case REG_BLDY: + value &= 0x1F; + if (value > 0x10) { + value = 0x10; + } + break; + case REG_WININ: + value &= 0x3F3F; + break; + case REG_WINOUT: + value &= 0x3F3F; + break; + default: + break; + } + glRenderer->shadowRegs[address >> 1] = value; + return value; +} + +void _cleanRegister(struct GBAVideoGLRenderer* glRenderer, int address, uint16_t value) { + switch (address) { + case REG_DISPCNT: glRenderer->dispcnt = value; GBAVideoGLRendererUpdateDISPCNT(glRenderer); break; case REG_BG0CNT: - value &= 0xDFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[0], value); break; case REG_BG1CNT: - value &= 0xDFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[1], value); break; case REG_BG2CNT: - value &= 0xFFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[2], value); break; case REG_BG3CNT: - value &= 0xFFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[3], value); break; case REG_BG0HOFS: - value &= 0x01FF; glRenderer->bg[0].x = value; break; case REG_BG0VOFS: - value &= 0x01FF; glRenderer->bg[0].y = value; break; case REG_BG1HOFS: - value &= 0x01FF; glRenderer->bg[1].x = value; break; case REG_BG1VOFS: - value &= 0x01FF; glRenderer->bg[1].y = value; break; case REG_BG2HOFS: - value &= 0x01FF; glRenderer->bg[2].x = value; break; case REG_BG2VOFS: - value &= 0x01FF; glRenderer->bg[2].y = value; break; case REG_BG3HOFS: - value &= 0x01FF; glRenderer->bg[3].x = value; break; case REG_BG3VOFS: - value &= 0x01FF; glRenderer->bg[3].y = value; break; case REG_BG2PA: @@ -911,10 +943,6 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, value &= 0x1F1F; break; case REG_BLDY: - value &= 0x1F; - if (value > 0x10) { - value = 0x10; - } glRenderer->bldy = value; break; case REG_WIN0H: @@ -970,12 +998,10 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, } break; case REG_WININ: - value &= 0x3F3F; glRenderer->winN[0].control = value; glRenderer->winN[1].control = value >> 8; break; case REG_WINOUT: - value &= 0x3F3F; glRenderer->winout = value; glRenderer->objwin = value >> 8; break; @@ -985,11 +1011,43 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, default: break; } - return value; } void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + + if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { + if (glRenderer->firstAffine < 0) { + memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); + glRenderer->firstAffine = y; + } + } else { + glRenderer->firstAffine = -1; + } + + if (glRenderer->paletteDirty || glRenderer->vramDirty || glRenderer->oamDirty || glRenderer->regsDirty) { + if (glRenderer->firstY >= 0) { + _drawScanlines(glRenderer, y - 1); + } + } + if (glRenderer->firstY < 0) { + glRenderer->firstY = y; + } + + int i; + for (i = 0; i < 0x30; ++i) { + if (!(glRenderer->regsDirty & (1ULL << i))) { + continue; + } + _cleanRegister(glRenderer, i << 1, glRenderer->shadowRegs[i]); + } + glRenderer->regsDirty = 0; + if (glRenderer->paletteDirty) { glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex); #ifdef BUILD_GLES3 @@ -999,7 +1057,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { #endif glRenderer->paletteDirty = false; } - int i; + int first = -1; glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); for (i = 0; i < 25; ++i) { @@ -1014,6 +1072,11 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { } glRenderer->vramDirty = 0; + if (glRenderer->oamDirty) { + glRenderer->oamMax = GBAVideoRendererCleanOAM(glRenderer->d.oam->obj, glRenderer->sprites, 0); + glRenderer->oamDirty = false; + } + if (y == 0) { memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); @@ -1030,11 +1093,32 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glClear(GL_COLOR_BUFFER_BIT); } } + + if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { + memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[2], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[1], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[1], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); + + glRenderer->bg[2].affine[0].sx += glRenderer->bg[2].affine[0].dmx; + glRenderer->bg[2].affine[0].sy += glRenderer->bg[2].affine[0].dmy; + glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx; + glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy; + glRenderer->regsDirty |= 1ULL << 0x30; + } + + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); +} + +void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) { glEnable(GL_SCISSOR_TEST); uint32_t backdrop = M_RGB5_TO_RGB8(glRenderer->d.palette[0]); glViewport(0, 0, 1, GBA_VIDEO_VERTICAL_PIXELS); - glScissor(0, y, 1, 1); + glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); glClearColor(((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); @@ -1044,30 +1128,12 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glClear(GL_COLOR_BUFFER_BIT); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); - if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { - if (glRenderer->firstAffine < 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - glRenderer->firstAffine = y; - } - } else { - glRenderer->firstAffine = -1; - } - GBAVideoGLRendererDrawWindow(glRenderer, y); if (GBARegisterDISPCNTIsObjEnable(glRenderer->dispcnt) && !glRenderer->d.disableOBJ) { - if (glRenderer->oamDirty) { - glRenderer->oamMax = GBAVideoRendererCleanOAM(glRenderer->d.oam->obj, glRenderer->sprites, 0); - glRenderer->oamDirty = false; - } int i; for (i = glRenderer->oamMax; i--;) { struct GBAVideoRendererSprite* sprite = &glRenderer->sprites[i]; - if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { + if ((y < sprite->y && (sprite->endY - 256 < 0 || glRenderer->firstY >= sprite->endY - 256)) || glRenderer->firstY >= sprite->endY) { continue; } @@ -1111,29 +1177,16 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { break; } } - - if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - - glRenderer->bg[2].affine[0].sx += glRenderer->bg[2].affine[0].dmx; - glRenderer->bg[2].affine[0].sy += glRenderer->bg[2].affine[0].dmy; - glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx; - glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy; - } - memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); - memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); + glRenderer->firstY = -1; } void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + _drawScanlines(glRenderer, GBA_VIDEO_VERTICAL_PIXELS - 1); _finalizeLayers(glRenderer); glBindVertexArray(0); glRenderer->firstAffine = -1; + glRenderer->firstY = -1; glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx; glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy; glRenderer->bg[3].affine[0].sx = glRenderer->bg[3].refx; @@ -1293,7 +1346,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB } if (!GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesBIsVFlip(sprite->b)) { - spriteY = (y - height) + (y - spriteY) + 1; + spriteY = (renderer->firstY - height) + (renderer->firstY - spriteY) + 1; } int totalWidth = width; @@ -1307,14 +1360,14 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB const GLuint* uniforms = shader->uniforms; glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OBJ]); glViewport(x * renderer->scale, spriteY * renderer->scale, totalWidth * renderer->scale, totalHeight * renderer->scale); - glScissor(x * renderer->scale, y * renderer->scale, totalWidth * renderer->scale, renderer->scale); + glScissor(x * renderer->scale, renderer->firstY * renderer->scale, totalWidth * renderer->scale, (y - renderer->firstY + 1) * renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->vramTex); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y - spriteY); + glUniform2i(uniforms[GBA_GL_VS_LOC], totalHeight, 0); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], (GBAObjAttributesBIsHFlip(sprite->b) && !GBAObjAttributesAIsTransformed(sprite->a)) ? -totalWidth : totalWidth, totalHeight); glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0); glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1); @@ -1379,26 +1432,50 @@ void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBa } void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { - int inY = y + background->y; - int yBase = inY & 0xFF; + int inY0 = renderer->firstY + background->y; + int yDiv = (((y + background->y) & ~0xFF) - background->y) & 0xFF; + int inY1 = yDiv + background->y; + int yBase0 = inY0 & 0xFF; + int yBase1 = inY1 & 0xFF; if (background->size == 2) { - yBase += inY & 0x100; + yBase0 += inY0 & 0x100; + yBase1 += inY1 & 0x100; } else if (background->size == 3) { - yBase += (inY & 0x100) << 1; + yBase0 += (inY0 & 0x100) << 1; + yBase1 += (inY1 & 0x100) << 1; } const struct GBAVideoGLShader* shader = &renderer->bgShader[background->multipalette ? 1 : 0]; const GLuint* uniforms = shader->uniforms; - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); _prepareBackground(renderer, background, uniforms); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); - glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + if (yDiv > renderer->firstY) { + int end = yDiv - 1; + if (end > y) { + end = y; + } + glScissor(0, renderer->firstY * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, (end - renderer->firstY + 1) * renderer->scale); + glUniform2i(uniforms[GBA_GL_VS_LOC], end - renderer->firstY + 1, renderer->firstY); + glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase0 - renderer->firstY); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + + if (y >= yDiv) { + int start = yDiv; + if (yDiv < renderer->firstY) { + start = renderer->firstY; + } + glScissor(0, start * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, (y - start + 1) * renderer->scale); + glUniform2i(uniforms[GBA_GL_VS_LOC], y - start + 1, start); + glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase1 - yDiv); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } @@ -1529,7 +1606,7 @@ static void _scissorWindow(int start, int end, int y, int lines, int scale) { glClear(GL_COLOR_BUFFER_BIT); } -static void _scissorWindowN(struct GBAVideoWindowRegion* region, int y, int scale) { +static void _scissorWindowN(const struct GBAVideoWindowRegion* region, const struct GBAVideoWindowRegion* v, const struct GBAVideoWindowRegion* y, int scale) { int sdelta = region[0].start - region[1].start; int edelta = region[0].end - region[1].end; int maxDelta = 0; @@ -1543,14 +1620,22 @@ static void _scissorWindowN(struct GBAVideoWindowRegion* region, int y, int scal } else if (-edelta > maxDelta) { maxDelta = -edelta; } + int startY = y->start; + int endY = y->end; + if (startY < v->start) { + startY = v->start; + } + if (endY >= v->end) { + endY = v->end - 1; + } if (!(sdelta | edelta) || maxDelta >= GBA_VIDEO_VERTICAL_PIXELS / 2) { - _scissorWindow(region[0].start * scale, region[0].end * scale, y, scale, scale); + _scissorWindow(region[0].start * scale, region[0].end * scale, startY * scale, (endY - startY + 1) * scale, scale); } else { int i; - for (i = 0; i < scale; ++i) { + for (i = 0; i < scale * (endY - startY + 1); ++i) { int start = region[1].start * scale + sdelta * i; int end = region[1].end * scale + edelta * i; - _scissorWindow(start, end, y + i, 1, scale); + _scissorWindow(start, end, startY * scale + i, 1, scale); } } } @@ -1565,17 +1650,21 @@ void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { _clearWindow(dispcnt, renderer->bldb, renderer->bldy); - _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->firstY * renderer->scale, (y - renderer->firstY + 1) * renderer->scale, renderer->scale); } else { _clearWindow(renderer->winout & dispcnt, renderer->bldb, renderer->bldy); - _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale, renderer->scale); - if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->firstY * renderer->scale, (y - renderer->firstY + 1) * renderer->scale, renderer->scale); + struct GBAVideoWindowRegion yRegion = { + y, + renderer->firstY + }; + if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && renderer->firstY < renderer->winN[1].v.end) { _clearWindow(renderer->winN[1].control & dispcnt, renderer->bldb, renderer->bldy); - _scissorWindowN(renderer->winN[1].h, y * renderer->scale, renderer->scale); + _scissorWindowN(renderer->winN[1].h, &renderer->winN[1].v, &yRegion, renderer->scale); } - if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { + if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && renderer->firstY < renderer->winN[0].v.end) { _clearWindow(renderer->winN[0].control & dispcnt, renderer->bldb, renderer->bldy); - _scissorWindowN(renderer->winN[0].h, y * renderer->scale, renderer->scale); + _scissorWindowN(renderer->winN[0].h, &renderer->winN[0].v, &yRegion, renderer->scale); } } } From 5cc4c752e59bdedb40feb64b9818d3d5baac6cd8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 20:37:04 -0700 Subject: [PATCH 57/63] GBA Video: Batch affine modifications --- include/mgba/internal/gba/renderers/gl.h | 6 +- src/gba/renderers/gl.c | 326 +++++++++++------------ 2 files changed, 167 insertions(+), 165 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index e6f8db74c..264c506b4 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -62,7 +62,7 @@ struct GBAVideoGLBackground { int32_t refx; int32_t refy; - struct GBAVideoGLAffine affine[4]; + struct GBAVideoGLAffine affine; }; enum { @@ -79,6 +79,8 @@ enum { GBA_GL_TEX_BACKDROP_COLOR, GBA_GL_TEX_BACKDROP_FLAGS, GBA_GL_TEX_WINDOW, + GBA_GL_TEX_AFFINE_2, + GBA_GL_TEX_AFFINE_3, GBA_GL_TEX_MAX }; @@ -94,7 +96,6 @@ enum { GBA_GL_BG_OFFSET, GBA_GL_BG_INFLAGS, GBA_GL_BG_TRANSFORM, - GBA_GL_BG_RANGE, GBA_GL_BG_MOSAIC, GBA_GL_OBJ_VRAM = 2, @@ -130,6 +131,7 @@ struct GBAVideoGLRenderer { uint32_t* temporaryBuffer; struct GBAVideoGLBackground bg[4]; + struct GBAVideoGLAffine affine[2][GBA_VIDEO_VERTICAL_PIXELS]; int oamMax; bool oamDirty; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index ed4b5a28d..c710c2111 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -181,7 +181,6 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, - { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -193,6 +192,25 @@ static const char* const _interpolate = " 3 * x1m * x1m * x * arr[1] +" " 3 * x1m * x * x * arr[2] +" " x * x * x * arr[3];\n" + "}\n" + + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]) {\n" + " mat[2] = texelFetch(transform, ivec2(0, y), 0).xz;\n" + " ivec4 splitOffset[4];\n" + " splitOffset[2] = texelFetch(transform, ivec2(1, y), 0);\n" + + " mat[0] = texelFetch(transform, ivec2(0, y - 2), 0).xz;\n" + " mat[1] = texelFetch(transform, ivec2(0, y - 1), 0).xz;\n" + " mat[3] = texelFetch(transform, ivec2(0, y + 1), 0).xz;\n" + + " splitOffset[0] = texelFetch(transform, ivec2(1, y - 2), 0);\n" + " splitOffset[1] = texelFetch(transform, ivec2(1, y - 1), 0);\n" + " splitOffset[3] = texelFetch(transform, ivec2(1, y + 1), 0);\n" + + " aff[0] = (splitOffset[0].xz & 0xFFFF) + (splitOffset[0].yw << 16);\n" + " aff[1] = (splitOffset[1].xz & 0xFFFF) + (splitOffset[1].yw << 16);\n" + " aff[2] = (splitOffset[2].xz & 0xFFFF) + (splitOffset[2].yw << 16);\n" + " aff[3] = (splitOffset[3].xz & 0xFFFF) + (splitOffset[3].yw << 16);\n" "}\n"; static const char* const _renderMode2 = @@ -203,9 +221,7 @@ static const char* const _renderMode2 = "uniform int charBase;\n" "uniform int size;\n" "uniform ivec4 inflags;\n" - "uniform ivec2[4] offset;\n" - "uniform ivec2[4] transform;\n" - "uniform vec2 range;\n" + "uniform isampler2D transform;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -215,6 +231,7 @@ static const char* const _renderMode2 = "vec4 fetchTile(ivec2 coord);\n" "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" "vec4 renderTile(ivec2 coord) {\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" @@ -234,6 +251,9 @@ static const char* const _renderMode2 = "}\n" "void main() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" " vec2 coord = texCoord;\n" " if (mosaic.x > 1) {\n" " coord.x -= mod(coord.x, mosaic.x);\n" @@ -241,9 +261,9 @@ static const char* const _renderMode2 = " if (mosaic.y > 1) {\n" " coord.y -= mod(coord.y, mosaic.y);\n" " }\n" - " float y = coord.y - range.x;\n" - " float lin = 0.5 - y / range.y * 0.25;\n" - " vec2 mixedTransform = interpolate(transform, lin);\n" + " float y = fract(coord.y);\n" + " float lin = 0.5 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " color = fetchTile(ivec2(mixedTransform * coord.x + mixedOffset));\n" " flags = inflags / flagCoeff;\n" @@ -258,7 +278,6 @@ static const struct GBAVideoGLUniform _uniformsMode35[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, - { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -269,9 +288,7 @@ static const char* const _renderMode35 = "uniform int charBase;\n" "uniform ivec2 size;\n" "uniform ivec4 inflags;\n" - "uniform ivec2[4] offset;\n" - "uniform ivec2[4] transform;\n" - "uniform vec2 range;\n" + "uniform isampler2D transform;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -280,8 +297,12 @@ static const char* const _renderMode35 = "precision highp int;\n" "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" "void main() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" " vec2 incoord = texCoord;\n" " if (mosaic.x > 1) {\n" " incoord.x -= mod(incoord.x, mosaic.x);\n" @@ -289,9 +310,9 @@ static const char* const _renderMode35 = " if (mosaic.y > 1) {\n" " incoord.y -= mod(incoord.y, mosaic.y);\n" " }\n" - " float y = incoord.y - range.x;\n" - " float lin = 0.5 - y / range.y * 0.25;\n" - " vec2 mixedTransform = interpolate(transform, lin);\n" + " float y = fract(incoord.y);\n" + " float lin = 0.5 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" @@ -317,7 +338,6 @@ static const struct GBAVideoGLUniform _uniformsMode4[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, - { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -329,9 +349,7 @@ static const char* const _renderMode4 = "uniform int charBase;\n" "uniform ivec2 size;\n" "uniform ivec4 inflags;\n" - "uniform ivec2[4] offset;\n" - "uniform ivec2[4] transform;\n" - "uniform vec2 range;\n" + "uniform isampler2D transform;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -340,8 +358,12 @@ static const char* const _renderMode4 = "precision highp int;\n" "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" "void main() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" " vec2 incoord = texCoord;\n" " if (mosaic.x > 1) {\n" " incoord.x -= mod(incoord.x, mosaic.x);\n" @@ -349,9 +371,9 @@ static const char* const _renderMode4 = " if (mosaic.y > 1) {\n" " incoord.y -= mod(incoord.y, mosaic.y);\n" " }\n" - " float y = incoord.y - range.x;\n" - " float lin = 0.5 - y / range.y * 0.25;\n" - " vec2 mixedTransform = interpolate(transform, lin);\n" + " float y = fract(incoord.y);\n" + " float lin = 0.5 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" @@ -615,6 +637,19 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, 256, 192, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_2]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_3]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_COLOR], GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); @@ -654,12 +689,12 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { bg->y = 0; bg->refx = 0; bg->refy = 0; - bg->affine[0].dx = 256; - bg->affine[0].dmx = 0; - bg->affine[0].dy = 0; - bg->affine[0].dmy = 256; - bg->affine[0].sx = 0; - bg->affine[0].sy = 0; + bg->affine.dx = 256; + bg->affine.dmx = 0; + bg->affine.dy = 0; + bg->affine.dmy = 256; + bg->affine.sx = 0; + bg->affine.sy = 0; glGenFramebuffers(1, &bg->fbo); glGenTextures(1, &bg->tex); glGenTextures(1, &bg->flags); @@ -796,7 +831,7 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, GBAVideoCacheWriteVideoRegister(renderer->cache, address, value); } - glRenderer->regsDirty |= 1ULL << (address >> 1); + bool dirty = true; switch (address) { case REG_DISPCNT: value &= 0xFFF7; @@ -815,6 +850,70 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, case REG_BG3VOFS: value &= 0x01FF; break; + case REG_BG2PA: + glRenderer->bg[2].affine.dx = value; + dirty = false; + break; + case REG_BG2PB: + glRenderer->bg[2].affine.dmx = value; + dirty = false; + break; + case REG_BG2PC: + glRenderer->bg[2].affine.dy = value; + dirty = false; + break; + case REG_BG2PD: + glRenderer->bg[2].affine.dmy = value; + dirty = false; + break; + case REG_BG2X_LO: + GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2X_HI: + GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2Y_LO: + GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2Y_HI: + GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG3PA: + glRenderer->bg[3].affine.dx = value; + dirty = false; + break; + case REG_BG3PB: + glRenderer->bg[3].affine.dmx = value; + dirty = false; + break; + case REG_BG3PC: + glRenderer->bg[3].affine.dy = value; + dirty = false; + break; + case REG_BG3PD: + glRenderer->bg[3].affine.dmy = value; + dirty = false; + break; + case REG_BG3X_LO: + GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3X_HI: + GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3Y_LO: + GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3Y_HI: + GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[3], value); + dirty = false; + break; case REG_BLDALPHA: value &= 0x1F1F; break; @@ -833,7 +932,14 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, default: break; } - glRenderer->shadowRegs[address >> 1] = value; + if (glRenderer->shadowRegs[address >> 1] == value) { + dirty = false; + } else { + glRenderer->shadowRegs[address >> 1] = value; + } + if (dirty) { + glRenderer->regsDirty |= 1ULL << (address >> 1); + } return value; } @@ -879,54 +985,6 @@ void _cleanRegister(struct GBAVideoGLRenderer* glRenderer, int address, uint16_t case REG_BG3VOFS: glRenderer->bg[3].y = value; break; - case REG_BG2PA: - glRenderer->bg[2].affine[0].dx = value; - break; - case REG_BG2PB: - glRenderer->bg[2].affine[0].dmx = value; - break; - case REG_BG2PC: - glRenderer->bg[2].affine[0].dy = value; - break; - case REG_BG2PD: - glRenderer->bg[2].affine[0].dmy = value; - break; - case REG_BG2X_LO: - GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[2], value); - break; - case REG_BG2X_HI: - GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[2], value); - break; - case REG_BG2Y_LO: - GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[2], value); - break; - case REG_BG2Y_HI: - GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[2], value); - break; - case REG_BG3PA: - glRenderer->bg[3].affine[0].dx = value; - break; - case REG_BG3PB: - glRenderer->bg[3].affine[0].dmx = value; - break; - case REG_BG3PC: - glRenderer->bg[3].affine[0].dy = value; - break; - case REG_BG3PD: - glRenderer->bg[3].affine[0].dmy = value; - break; - case REG_BG3X_LO: - GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[3], value); - break; - case REG_BG3X_HI: - GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[3], value); - break; - case REG_BG3Y_LO: - GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[3], value); - break; - case REG_BG3Y_HI: - GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[3], value); - break; case REG_BLDCNT: GBAVideoGLRendererWriteBLDCNT(glRenderer, value); value &= 0x3FFF; @@ -1016,14 +1074,10 @@ void _cleanRegister(struct GBAVideoGLRenderer* glRenderer, int address, uint16_t void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + memcpy(&glRenderer->affine[0][y], &glRenderer->bg[2].affine, sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->affine[1][y], &glRenderer->bg[3].affine, sizeof(struct GBAVideoGLAffine)); if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { if (glRenderer->firstAffine < 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); glRenderer->firstAffine = y; } } else { @@ -1039,6 +1093,9 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glRenderer->firstY = y; } + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); + int i; for (i = 0; i < 0x30; ++i) { if (!(glRenderer->regsDirty & (1ULL << i))) { @@ -1095,25 +1152,20 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { } if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - - glRenderer->bg[2].affine[0].sx += glRenderer->bg[2].affine[0].dmx; - glRenderer->bg[2].affine[0].sy += glRenderer->bg[2].affine[0].dmy; - glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx; - glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy; - glRenderer->regsDirty |= 1ULL << 0x30; + glRenderer->bg[2].affine.sx += glRenderer->bg[2].affine.dmx; + glRenderer->bg[2].affine.sy += glRenderer->bg[2].affine.dmy; + glRenderer->bg[3].affine.sx += glRenderer->bg[3].affine.dmx; + glRenderer->bg[3].affine.sy += glRenderer->bg[3].affine.dmy; } - - memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); - memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); } void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) { + if (glRenderer->firstAffine >= 0) { + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_2]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, glRenderer->affine[0]); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_3]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, glRenderer->affine[1]); + } glEnable(GL_SCISSOR_TEST); uint32_t backdrop = M_RGB5_TO_RGB8(glRenderer->d.palette[0]); @@ -1187,10 +1239,10 @@ void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { glBindVertexArray(0); glRenderer->firstAffine = -1; glRenderer->firstY = -1; - glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx; - glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy; - glRenderer->bg[3].affine[0].sx = glRenderer->bg[3].refx; - glRenderer->bg[3].affine[0].sy = glRenderer->bg[3].refy; + glRenderer->bg[2].affine.sx = glRenderer->bg[2].refx; + glRenderer->bg[2].affine.sy = glRenderer->bg[2].refy; + glRenderer->bg[3].affine.sx = glRenderer->bg[3].refx; + glRenderer->bg[3].affine.sy = glRenderer->bg[3].refy; } void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -1245,26 +1297,26 @@ static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16 static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refx = (bg->refx & 0xFFFF0000) | value; - bg->affine[0].sx = bg->refx; + bg->affine.sx = bg->refx; } static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refx = (bg->refx & 0x0000FFFF) | (value << 16); bg->refx <<= 4; bg->refx >>= 4; - bg->affine[0].sx = bg->refx; + bg->affine.sx = bg->refx; } static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refy = (bg->refy & 0xFFFF0000) | value; - bg->affine[0].sy = bg->refy; + bg->affine.sy = bg->refy; } static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refy = (bg->refy & 0x0000FFFF) | (value << 16); bg->refy <<= 4; bg->refy >>= 4; - bg->affine[0].sy = bg->refy; + bg->affine.sy = bg->refy; } static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value) { @@ -1480,64 +1532,12 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, } void _prepareTransform(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { - int reverse = 0; - int forward = 1; - if (renderer->scale > 1) { - switch (y - renderer->firstAffine) { - case 0: - case 1: - case 2: - case 3: - return; - case 4: - forward = 2; - reverse = 4; - break; - case 5: - forward = 2; - reverse = 3; - break; - case 6: - forward = 2; - reverse = 2; - break; - case 7: - forward = 2; - reverse = 1; - break; - } - } + glScissor(0, renderer->firstY * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * (y - renderer->firstY + 1)); + glUniform2i(uniforms[GBA_GL_VS_LOC], y - renderer->firstY + 1, renderer->firstY); - glScissor(0, (y - reverse) * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * forward); - glUniform2i(uniforms[GBA_GL_VS_LOC], forward, y - reverse); - glUniform2f(uniforms[GBA_GL_BG_RANGE], y - 1, 1); - if (renderer->scale > 1) { - glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { - background->affine[0].sx, background->affine[0].sy, - background->affine[1].sx, background->affine[1].sy, - background->affine[2].sx, background->affine[2].sy, - background->affine[3].sx, background->affine[3].sy, - }); - glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) { - background->affine[0].dx, background->affine[0].dy, - background->affine[1].dx, background->affine[1].dy, - background->affine[2].dx, background->affine[2].dy, - background->affine[3].dx, background->affine[3].dy, - }); - } else { - glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - }); - glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) { - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - }); - } + glActiveTexture(GL_TEXTURE0 + 2); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_AFFINE_2 + background->index - 2]); + glUniform1i(uniforms[GBA_GL_BG_TRANSFORM], 2); _prepareBackground(renderer, background, uniforms); } From d4c6472101237c51dbc547b6843a5c589f294ed4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 20:38:01 -0700 Subject: [PATCH 58/63] Qt: Fix doneCurrent location --- src/platform/qt/DisplayGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 77439874a..54b6bc59c 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -264,8 +264,8 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent if (!painter->m_gl->isValid()) { return; } - painter->m_gl->doneCurrent(); painter->m_gl->swapBuffers(painter->m_surface); + painter->m_gl->doneCurrent(); painter->m_gl->makeCurrent(painter->m_surface); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); From 75c212b23cf6a15a0cf4da5aa3ea4e186fabcb95 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 May 2019 21:15:23 -0700 Subject: [PATCH 59/63] GBA Video: Extrapolate edges in affine --- include/mgba/internal/gba/renderers/gl.h | 1 + src/gba/renderers/gl.c | 69 ++++++++++++++++++++---- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 264c506b4..cf53c6566 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -96,6 +96,7 @@ enum { GBA_GL_BG_OFFSET, GBA_GL_BG_INFLAGS, GBA_GL_BG_TRANSFORM, + GBA_GL_BG_RANGE, GBA_GL_BG_MOSAIC, GBA_GL_OBJ_VRAM = 2, diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index c710c2111..4fb3740a9 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -181,6 +181,7 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -195,22 +196,62 @@ static const char* const _interpolate = "}\n" "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]) {\n" - " mat[2] = texelFetch(transform, ivec2(0, y), 0).xz;\n" + " int start = max(range.x, y - 3);\n" " ivec4 splitOffset[4];\n" - " splitOffset[2] = texelFetch(transform, ivec2(1, y), 0);\n" - " mat[0] = texelFetch(transform, ivec2(0, y - 2), 0).xz;\n" - " mat[1] = texelFetch(transform, ivec2(0, y - 1), 0).xz;\n" - " mat[3] = texelFetch(transform, ivec2(0, y + 1), 0).xz;\n" + " mat[0] = texelFetch(transform, ivec2(0, start), 0).xz;\n" + " mat[1] = texelFetch(transform, ivec2(0, start + 1), 0).xz;\n" + " mat[2] = texelFetch(transform, ivec2(0, start + 2), 0).xz;\n" + " mat[3] = texelFetch(transform, ivec2(0, start + 3), 0).xz;\n" - " splitOffset[0] = texelFetch(transform, ivec2(1, y - 2), 0);\n" - " splitOffset[1] = texelFetch(transform, ivec2(1, y - 1), 0);\n" - " splitOffset[3] = texelFetch(transform, ivec2(1, y + 1), 0);\n" + " splitOffset[0] = texelFetch(transform, ivec2(1, start + 0), 0);\n" + " splitOffset[1] = texelFetch(transform, ivec2(1, start + 1), 0);\n" + " splitOffset[2] = texelFetch(transform, ivec2(1, start + 2), 0);\n" + " splitOffset[3] = texelFetch(transform, ivec2(1, start + 3), 0);\n" " aff[0] = (splitOffset[0].xz & 0xFFFF) + (splitOffset[0].yw << 16);\n" " aff[1] = (splitOffset[1].xz & 0xFFFF) + (splitOffset[1].yw << 16);\n" " aff[2] = (splitOffset[2].xz & 0xFFFF) + (splitOffset[2].yw << 16);\n" " aff[3] = (splitOffset[3].xz & 0xFFFF) + (splitOffset[3].yw << 16);\n" + + " if (y - 3 < range.x) {\n" + " ivec2 tempMat[3];\n" + " ivec2 tempAff[3];\n" + " tempMat[0] = ivec2(interpolate(mat, -0.75));\n" + " tempMat[1] = ivec2(interpolate(mat, -0.5));\n" + " tempMat[2] = ivec2(interpolate(mat, -0.25));\n" + " tempAff[0] = ivec2(interpolate(aff, -0.75));\n" + " tempAff[1] = ivec2(interpolate(aff, -0.5));\n" + " tempAff[2] = ivec2(interpolate(aff, -0.25));\n" + " if (range.x == y) {\n" + " mat[3] = mat[0];\n" + " mat[2] = tempMat[2];\n" + " mat[1] = tempMat[1];\n" + " mat[0] = tempMat[0];\n" + " aff[3] = aff[0];\n" + " aff[2] = tempAff[2];\n" + " aff[1] = tempAff[1];\n" + " aff[0] = tempAff[0];\n" + " } else if (range.x == y - 1) {\n" + " mat[3] = mat[1];\n" + " mat[2] = mat[0];\n" + " mat[1] = tempMat[2];\n" + " mat[0] = tempMat[1];\n" + " aff[3] = aff[1];\n" + " aff[2] = aff[0];\n" + " aff[1] = tempAff[2];\n" + " aff[0] = tempAff[1];\n" + " } else if (range.x == y - 2) {\n" + " mat[3] = mat[2];\n" + " mat[2] = mat[1];\n" + " mat[1] = mat[0];\n" + " mat[0] = tempMat[0];\n" + " aff[3] = aff[2];\n" + " aff[2] = aff[1];\n" + " aff[1] = aff[0];\n" + " aff[0] = tempAff[0];\n" + " }\n" + " }\n" "}\n"; static const char* const _renderMode2 = @@ -222,6 +263,7 @@ static const char* const _renderMode2 = "uniform int size;\n" "uniform ivec4 inflags;\n" "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -262,7 +304,7 @@ static const char* const _renderMode2 = " coord.y -= mod(coord.y, mosaic.y);\n" " }\n" " float y = fract(coord.y);\n" - " float lin = 0.5 + y * 0.25;\n" + " float lin = 0.75 + y * 0.25;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " color = fetchTile(ivec2(mixedTransform * coord.x + mixedOffset));\n" @@ -278,6 +320,7 @@ static const struct GBAVideoGLUniform _uniformsMode35[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -289,6 +332,7 @@ static const char* const _renderMode35 = "uniform ivec2 size;\n" "uniform ivec4 inflags;\n" "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -311,7 +355,7 @@ static const char* const _renderMode35 = " incoord.y -= mod(incoord.y, mosaic.y);\n" " }\n" " float y = fract(incoord.y);\n" - " float lin = 0.5 + y * 0.25;\n" + " float lin = 0.75 + y * 0.25;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" @@ -338,6 +382,7 @@ static const struct GBAVideoGLUniform _uniformsMode4[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -350,6 +395,7 @@ static const char* const _renderMode4 = "uniform ivec2 size;\n" "uniform ivec4 inflags;\n" "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" @@ -372,7 +418,7 @@ static const char* const _renderMode4 = " incoord.y -= mod(incoord.y, mosaic.y);\n" " }\n" " float y = fract(incoord.y);\n" - " float lin = 0.5 + y * 0.25;\n" + " float lin = 0.75 + y * 0.25;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" @@ -1534,6 +1580,7 @@ void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, void _prepareTransform(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { glScissor(0, renderer->firstY * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * (y - renderer->firstY + 1)); glUniform2i(uniforms[GBA_GL_VS_LOC], y - renderer->firstY + 1, renderer->firstY); + glUniform2i(uniforms[GBA_GL_BG_RANGE], renderer->firstAffine, y); glActiveTexture(GL_TEXTURE0 + 2); glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_AFFINE_2 + background->index - 2]); From 5b040e236db0dd39e2e29ad7724b9712ac84c57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Doncam=20Demian=20L=C3=B3pez=20Brante?= Date: Wed, 22 May 2019 21:18:22 -0400 Subject: [PATCH 60/63] Qt: Updated Spanish translation --- src/platform/qt/ts/mgba-es.ts | 1111 +++++++++++++++++++++------------ 1 file changed, 712 insertions(+), 399 deletions(-) diff --git a/src/platform/qt/ts/mgba-es.ts b/src/platform/qt/ts/mgba-es.ts index cae9fd4f7..2e950a8df 100644 --- a/src/platform/qt/ts/mgba-es.ts +++ b/src/platform/qt/ts/mgba-es.ts @@ -30,9 +30,15 @@ + © 2013 – 2019 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 +Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + © 2013 – 2019 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 +Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + + © 2013 – 2018 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 Game Boy Advance is a registered trademark of Nintendo Co., Ltd. - © 2013 – 2018 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 + © 2013 – 2018 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 Game Boy Advance es una marca registrada de Nintendo Co., Ltd. @@ -125,6 +131,84 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. 0x00 (00) + + BattleChipView + + + BattleChip Gate + BattleChip Gate + + + + Chip name + Nombre del chip + + + + Insert + Insertar + + + + Save + Guardar + + + + Load + Cargar + + + + Add + Agregar + + + + Remove + Quitar + + + + Gate type + Tipo de Gate + + + + Ba&ttleChip Gate + Ba&ttleChip Gate + + + + Progress &Gate + Progress &Gate + + + + Beast &Link Gate + Beast &Link Gate + + + + Inserted + Insertado + + + + Chip ID + ID de chip + + + + Update Chip data + Actualizar datos del chip + + + + Show advanced + Mostrar ajustes avanzados + + CheatsView @@ -201,12 +285,12 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Frameskip - Salto de cuadros + Salto Frame delay (ms) - Retraso entre cuadros (ms) + Retraso (ms) @@ -219,7 +303,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. I/O Viewer - Visor de I/O + Visor de E/S @@ -417,7 +501,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Debug - Depuración (Debug) + Debug @@ -427,12 +511,12 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Info - Información (Info) + Info Warning - Advertencia (Warning) + Warning @@ -457,7 +541,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Max Lines - Máximo de líneas + Líneas max. @@ -631,57 +715,69 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Alinear a: - 1 Byte - 1 byte + 1 byte + + + 2 Bytes + 2 bytes + + + 4 Bytes + 4 bytes + + + + &1 Byte + - 2 Bytes - 2 bytes + &2 Bytes + - 4 Bytes - 4 bytes + &4 Bytes + - + Unsigned Integer: Entero sin signo: - + Signed Integer: Entero con signo: - + String: - Cadena: + Cadena de texto: - + Load TBL Cargar TBL - + Copy Selection Copiar selección - + Paste Pegar - + Save Selection Guardar selección - + Load Cargar @@ -717,7 +813,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Transform - Transformación + Transform @@ -936,7 +1032,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Super Game Boy (SGB) - + Super Game Boy (SGB) @@ -1203,6 +1299,35 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. No se puede iniciar un procesador de audio sin entrada + + QGBA::BattleChipView + + + BattleChip data missing + Datos del BattleChip no encontrados + + + + BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? + Faltan los datos de BattleChip. Las BattleChip Gates seguirán funcionando, pero faltarán algunos gráficos. ¿Quieres descargar los datos ahora? + + + + + Select deck file + Elegir archivo de baraja + + + + Incompatible deck + Baraja incompatible + + + + The selected deck is not compatible with this Chip Gate + La baraja seleccionada no es compatible con esta Chip Gate + + QGBA::CheatsModel @@ -1249,22 +1374,22 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Error al abrir el archivo de guardado: %1 - + Failed to open game file: %1 Error al abrir el archivo del juego: %1 - + Failed to open snapshot file for reading: %1 Error al leer del archivo de captura: %1 - + Failed to open snapshot file for writing: %1 Error al escribir al archivo de captura: %1 @@ -1277,12 +1402,20 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Error al abrir el archivo del juego: %1 + + QGBA::GBAApp + + + Enable Discord Rich Presence + Habilitar Rich Presence en Discord + + QGBA::GBAKeyEditor Clear Button - Limpiar botón + Limpiar botones @@ -2789,40 +2922,94 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Espacio %1 + + QGBA::LogConfigModel + + + + Default + Por defecto + + + + Fatal + Fatal + + + + Error + Error + + + + Warning + Advertencia (Warning) + + + + Info + Información (Info) + + + + Debug + Depuración (Debug) + + + + Stub + Stub + + + + Game Error + Error de juego + + QGBA::LogController - + + [%1] %2: %3 + [%1] %2: %3 + + + + An error occurred + Ocurrió un error + + + DEBUG DEPURACIÓN - + STUB STUB - + INFO INFORMACIÓN - + WARN ADVERTENCIA - + ERROR ERROR - + FATAL FATAL - + GAME ERROR ERROR DE JUEGO @@ -2830,47 +3017,47 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::MapView - + Map Addr. Dir de mapa - + Mirror Espejar - + None Ninguno - + Both Ambos - + Horizontal Horizontal - + Vertical Vertical - + Export map Exportar mapa - + Portable Network Graphics (*.png) Gráficos de red portátiles (*.png) - + Failed to open output PNG file: %1 Error al abrir el archivo PNG de salida: %1 @@ -2976,54 +3163,54 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ObjView - - + + 0x%0 0x%0 - + Off No - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Inválido - - + + N/A n/d - + Export sprite Exportar sprite - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 Error al abrir el archivo PNG de salida: %1 @@ -3107,59 +3294,59 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::SettingsView - - + + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (forzar versión 1.x) - + None (Still Image) Nada (imagen estática) - + Keyboard Teclado - + Controllers Controladores - + Shortcuts Atajos de teclado - - + + Shaders Shaders - + Select BIOS Seleccionar BIOS @@ -3200,17 +3387,32 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ShortcutController - + Action + Acción + + + Keyboard + Teclado + + + Gamepad + Mando + + + + QGBA::ShortcutModel + + Action Acción - + Keyboard Teclado - + Gamepad Mando @@ -3236,108 +3438,108 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) ROMs de Game Boy Advance (%1) - + Game Boy ROMs (%1) ROMs de Game Boy (%1) - + All ROMs (%1) Todas las ROMs (%1) - + %1 Video Logs (*.mvl) Video-registros de %1 (*.mvl) - + Archives (%1) Contenedores (%1) - - - + + + Select ROM Seleccionar ROM - + Select folder Seleccionar carpeta - + Game Boy Advance save files (%1) Archivos de guardado de Game Boy Advance (%1) - - - + + + Select save Seleccionar guardado - + mGBA savestate files (%1) Archivos de estado de guardado de mGBA (%1) - - + + Select savestate Elegir estado de guardado - + Select patch Seleccionar parche - + Patches (*.ips *.ups *.bps) Parches (*.ips *.ups *.bps) - + Select image Seleccionar imagen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Archivo de imagen (*.png *.gif *.jpg *.jpeg);;Todos los archivos (*) - - + + GameShark saves (*.sps *.xps) Guardados de GameShark (*.sps *.xps) - + Select video log Seleccionar video-registro - + Video logs (*.mvl) Video-registros (*.mvl) - + Crash Error fatal - + The game has crashed with the following error: %1 @@ -3346,433 +3548,424 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. %1 - + Couldn't Load No se pudo cargar - + Could not load game. Are you sure it's in the correct format? No se pudo cargar el juego. ¿Estás seguro de que está en el formato correcto? - + Unimplemented BIOS call Llamada a BIOS no implementada - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Este juego utiliza una llamada al BIOS que no se ha implementado. Utiliza el BIOS oficial para obtener la mejor experiencia. - + Really make portable? ¿Hacer "portable"? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Esto hará que el emulador cargue su configuración desde el mismo directorio que el ejecutable. ¿Quieres continuar? - + Restart needed Reinicio necesario - + Some changes will not take effect until the emulator is restarted. Algunos cambios no surtirán efecto hasta que se reinicie el emulador. - + - Player %1 of %2 - Jugador %1 de %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 fps) - %4 - + &File &Archivo - + Load &ROM... Cargar &ROM... - + Load ROM in archive... Cargar ROM desde contenedor... - + Add folder to library... Agregar carpeta a la biblioteca... - + Load alternate save... Cargar guardado alternativo... - + Load temporary save... Cargar guardado temporal... - + Load &patch... Cargar &parche... - + Boot BIOS Arrancar BIOS - + Replace ROM... Reemplazar ROM... - + ROM &info... &Información de la ROM... - + Recent Recientes - + Make portable Hacer "portable" - + &Load state Ca&rgar estado - - F10 - F10 + + About... + Acerca de... - + F10 + F10 + + + Load state file... Cargar archivo de estado... - + &Save state Guardar e&stado - Shift+F10 - Shift+F10 + Shift+F10 - + Save state file... Guardar archivo de estado... - + Quick load Cargado rápido - + Quick save Guardado rápido - + Load recent Cargar reciente - + Save recent Guardar reciente - + Undo load state Deshacer cargar estado - F11 - F11 + F11 - + Undo save state Deshacer guardar estado - Shift+F11 - Shift+F11 + Shift+F11 - - + + State &%1 Estado &%1 - F%1 - F%1 + F%1 + + + Shift+F%1 + Shift+F%1 - Shift+F%1 - Shift+F%1 - - - Load camera image... Cargar imagen para la cámara... - + Import GameShark Save Importar guardado de GameShark - + Export GameShark Save Exportar guardado de GameShark - + New multiplayer window Nueva ventana multijugador - About - Acerca de + Acerca de - + E&xit Salir (&X) - + &Emulation &Emulación - + &Reset &Reinicializar - Ctrl+R - Ctrl+R + Ctrl+R - + Sh&utdown Apagar (&U) - + Yank game pak Tirar del cartucho - + &Pause &Pausar - Ctrl+P - Ctrl+P + Ctrl+P - + &Next frame Cuadro siguie&nte - Ctrl+N - Ctrl+N + Ctrl+N - + Fast forward (held) Avance rápido (mantener) - + &Fast forward &Avance rápido - Shift+Tab - Shift+Tab + Shift+Tab - + Fast forward speed Velocidad de avance rápido - + Unbounded Sin límite - + %0x %0x - + Rewind (held) Rebobinar (mantener) - + Re&wind Re&bobinar - ~ - ~ + ~ - + Step backwards Paso hacia atrás - Ctrl+B - Ctrl+B + Ctrl+B - + Sync to &video Sincronizar a &video - + Sync to &audio Sincronizar a au&dio - + Solar sensor Sensor solar - + Increase solar level Subir nivel - + Decrease solar level Bajar nivel - + Brightest solar level Más claro - + Darkest solar level Más oscuro - + Brightness %1 Brillo %1 - + Audio/&Video Audio/&video - + Frame size Tamaño del cuadro - %1x - %1x + %1x - + Toggle fullscreen Pantalla completa - + Lock aspect ratio Bloquear proporción de aspecto - + Force integer scaling Forzar escala a enteros - + Bilinear filtering Filtro bilineal - + Frame&skip &Salto de cuadros - + Mute Silenciar - + FPS target Objetivo de FPS - + Native (59.7275) Nativo (59,7275) @@ -3809,192 +4002,214 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. 240 - + Take &screenshot Tomar pan&tallazo - + F12 F12 - Record output... - Grabar salida... + Grabar salida... - + Record GIF... Grabar GIF... - Record video log... - Grabar video-registro... + Grabar video-registro... - Stop video log - Detener video-registro + Detener video-registro - + Game Boy Printer... Game Boy Printer... - + + BattleChip Gate... + BattleChip Gate... + + + + %1× + %1× + + + + Record A/V... + Grabar A/V... + + + Video layers Capas de video - + Audio channels Canales de audio - + Adjust layer placement... Ajustar ubicación de capas... - + &Tools Herramien&tas - + View &logs... Ver re&gistros... - + Game &overrides... Ajustes específic&os por juego... - + Game &Pak sensors... Sensores del Game &Pak... - + &Cheats... Tru&cos... - + Settings... Ajustes... - + Open debugger console... Abrir consola de depuración... - + Start &GDB server... Iniciar servidor &GDB... - + View &palette... Ver &paleta... - + View &sprites... Ver &sprites... - + View &tiles... Ver &tiles... - + View &map... Ver &mapa... - + View memory... Ver memoria... - + Search memory... Buscar memoria... - + View &I/O registers... Ver registros &I/O... - + + Record debug video log... + Grabar registro de depuración de video... + + + + Stop debug video log + Detener registro de depuración de video + + + Exit fullscreen Salir de pantalla completa - + GameShark Button (held) Botón GameShark (mantener) - + Autofire Disparo automático - + Autofire A Disparo automático A - + Autofire B Disparo automático B - + Autofire L Disparo automático L - + Autofire R Disparo automático R - + Autofire Start Disparo automático Start - + Autofire Select Disparo automático Select - + Autofire Up Disparo automático Arriba - + Autofire Right Disparo automático Derecha - + Autofire Down Disparo automático Abajo - + Autofire Left Disparo automático Izquierda @@ -4158,505 +4373,588 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Ajustes - + Audio/Video Audio/video - + Interface Interfaz - + Emulation Emulación - + + Enhancements + Mejoras + + + BIOS BIOS - + Paths Rutas - + + Logging + Registros + + + Game Boy Game Boy - + Audio driver: Sistema de audio: - + Audio buffer: Búfer de audio: - - + + 1536 1536 - + 512 512 - + 768 768 - + 1024 1024 - + 2048 2048 - + 3072 3072 - + 4096 4096 - + samples muestras - + Sample rate: Tasa de muestreo: - - + + 44100 44100 - + 22050 22050 - + 32000 32000 - + 48000 48000 - + Hz Hz - + Volume: Volumen: - - + + Mute Silenciar - + Fast forward volume: Vol. durante av. rápido: - + Display driver: Sistema de video: - + Frameskip: Salto de cuadros: - + Skip every Saltar cada - - + + frames cuadros - + FPS target: Objetivo de FPS: - + frames per second cuadros por segundo - + Sync: Sincronizar con: - + Video Video - + Audio Audio - + Lock aspect ratio Bloquear proporción de aspecto - + Bilinear filtering Filtro bilineal - - Force integer scaling - Forzar escalado de enteros + + Log to file + Guardar a archivo - + + Log to console + Guardar a consola + + + + Select Log File + Seleccionar + + + + Game Boy model: + Modelo de Game Boy: + + + + Super Game Boy model: + Modelo de Super Game Boy: + + + + Game Boy Color model: + Modelo de Game Boy Color: + + + + Use GBC colors in GB games + Usar colores de GBC en juegos GB + + + + Camera: + Cámara: + + + + Force integer scaling + Forzar escalado a valores enteros + + + Language Idioma - + English English - + Library: Biblioteca: - + List view Lista - + Tree view Árbol - + Show when no game open Mostrar cuando no haya un juego abierto - + Clear cache Limpiar caché - + Allow opposing input directions Permitir direcciones opuestas al mismo tiempo - + Suspend screensaver Suspender protector de pantalla - + Pause when inactive Pausar al no estar activo - + Show FPS in title bar Mostrar FPS en la barra de título - + Automatically save cheats Guardar trucos automáticamente - + Automatically load cheats Cargar trucos automáticamente - + Automatically save state Guardar estado automáticamente - + Automatically load state Cargar estado automáticamente - - Fast forward speed: - Velocidad de avance rápido: + + Enable Discord Rich Presence + Hablitar Rich Presence en Discord - + + Fast forward speed: + Avance rápido: + + + + × × - + Unbounded Sin límite - + Enable rewind Habilitar el rebobinar - + Rewind history: - Historial de rebobinado: + Hist. de rebobinado: - + Idle loops: Bucles inactivos: - + Run all Ejecutarlos todos - + Remove known Eliminar los conocidos - + Detect and remove Detectar y eliminar - + Savestate extra data: - Guardar datos extra con el estado: + Guardar datos extra: - - + + Screenshot Pantallazo - - + + Save data Datos de guardado - - + + Cheat codes Trucos - + Load extra data: - Cargar datos extra con el estado: + Cargar datos extra: Rewind affects save data El rebobinar afecta los datos de guardado - + Preload entire ROM into memory Cargar ROM completa a la memoria - + Autofire interval: - Intervalo de disparo automático: + Intervalo de turbo: - + + Video renderer: + Renderizador de video: + + + + Software + Software + + + + OpenGL + OpenGL + + + + OpenGL enhancements + Mejoras para OpenGL + + + + High-resolution scale: + Escala de alta resolución: + + + + XQ GBA audio (experimental) + Mejorar audio GBA (experimental) + + + GB BIOS file: Archivo BIOS GB: - - - - - - - - - + + + + + + + + + Browse Examinar - + Use BIOS file if found Usar archivo BIOS si fue encontrado - + Skip BIOS intro Saltar animación de entrada del BIOS - + GBA BIOS file: Archivo BIOS GBA: - + GBC BIOS file: Archivo BIOS GBC: - + SGB BIOS file: - SGB BIOS file: + Archivo BIOS SGB: - + Save games Datos de guardado - - - - - + + + + + Same directory as the ROM Al mismo directorio que la ROM - + Save states Estados de guardado - + Screenshots Pantallazos - + Patches Parches - + Cheats Trucos - Game Boy model - Modelo de Game Boy + Modelo de Game Boy - - - + + + Autodetect Detección automática - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - Super Game Boy model - Modelo de Super Game Boy + Modelo de Super Game Boy - Game Boy Color model - Modelo de Game Boy Color + Modelo de Game Boy Color - + Default BG colors: Colores de fondo por defecto: - + Super Game Boy borders Bordes de Super Game Boy - + Camera driver: Controlador de cámara: - + Default sprite colors 1: Colores de sprite 1 por defecto: - + Default sprite colors 2: Colores de sprite 2 por defecto: @@ -4730,20 +5028,30 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Tiles - + 256 colors 256 colores - + × × - + Magnification Ampliación + + + Tiles per row + Tiles por fila + + + + Fit to window + Ajustar a ventana + VideoView @@ -4882,86 +5190,91 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + HEVC (NVENC) + HEVC (NVENC) + + + VP8 VP8 - + VP9 VP9 - + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Sin comprimir - + Bitrate (kbps) Tasa de bits (kbps) - + VBR VBR - + ABR ABR - + Dimensions Dimensiones - + : : - + × × - + Lock aspect ratio Bloquear proporción de aspecto - + Show advanced Mostrar ajustes avanzados From 13ffd13cdd841571b5c2c0547611b7a3ea4467e8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 23 May 2019 00:04:26 -0700 Subject: [PATCH 61/63] Qt: Improve sync code --- CHANGES | 1 + src/platform/qt/Display.cpp | 2 +- src/platform/qt/DisplayGL.cpp | 65 ++++++++++++++++++++++------------- src/platform/qt/DisplayGL.h | 9 +++-- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index f5c0f18e2..c95593224 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,7 @@ Misc: - FFmpeg: Support audio-only recording - Qt: Increase maximum magnifications and scaling - Qt: Add native FPS button to settings view + - Qt: Improve sync code 0.7.1: (2019-02-24) Bugfixes: diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index ed7c1fd38..afc0cc5cf 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -20,7 +20,7 @@ Display* Display::create(QWidget* parent) { #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) QSurfaceFormat format; format.setSwapInterval(1); - format.setSwapBehavior(QSurfaceFormat::SingleBuffer); + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); #endif switch (s_driver) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 54b6bc59c..71398e6af 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2019 Jeffrey Pfau * * 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 @@ -261,15 +261,9 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent #endif m_backend->swap = [](VideoBackend* v) { PainterGL* painter = static_cast(v->user); - if (!painter->m_gl->isValid()) { - return; + if (!painter->m_swapTimer.isActive()) { + QMetaObject::invokeMethod(&painter->m_swapTimer, "start"); } - painter->m_gl->swapBuffers(painter->m_surface); - painter->m_gl->doneCurrent(); - painter->m_gl->makeCurrent(painter->m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif }; m_backend->init(m_backend, 0); @@ -287,6 +281,10 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent for (int i = 0; i < 2; ++i) { m_free.append(new uint32_t[1024 * 2048]); } + + m_swapTimer.setInterval(16); + m_swapTimer.setSingleShot(true); + connect(&m_swapTimer, &QTimer::timeout, this, &PainterGL::swap); } PainterGL::~PainterGL() { @@ -375,27 +373,22 @@ void PainterGL::draw() { return; } + if (m_needsUnlock) { + QTimer::singleShot(0, this, &PainterGL::draw); + return; + } + if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) { dequeue(); - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); - m_painter.begin(m_window); - performDraw(); - m_painter.end(); - m_backend->swap(m_backend); - if (!m_delayTimer.isValid()) { - m_delayTimer.start(); - } else if (m_gl->format().swapInterval() && m_context->videoSync()) { - while (m_delayTimer.elapsed() < 15) { - QThread::usleep(100); - } - m_delayTimer.restart(); + forceDraw(); + if (m_context->thread()->impl->sync.videoFrameWait) { + m_needsUnlock = true; + } else { + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } } else { mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } - if (!m_queue.isEmpty()) { - QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); - } } void PainterGL::forceDraw() { @@ -438,6 +431,30 @@ void PainterGL::performDraw() { if (m_messagePainter) { m_messagePainter->paint(&m_painter); } + m_frameReady = true; +} + +void PainterGL::swap() { + if (!m_gl->isValid()) { + return; + } + if (m_frameReady) { + m_gl->swapBuffers(m_surface); + m_gl->makeCurrent(m_surface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + m_frameReady = false; + } + if (m_needsUnlock) { + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); + m_needsUnlock = false; + } + if (!m_queue.isEmpty()) { + QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); + } else { + m_swapTimer.start(); + } } void PainterGL::enqueue(const uint32_t* backing) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 2dbd1f2f0..2c54934f4 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -16,13 +16,13 @@ #endif #endif -#include #include #include #include #include #include #include +#include #include "VideoProxy.h" @@ -105,6 +105,9 @@ public slots: int glTex(); +private slots: + void swap(); + private: void performDraw(); void dequeue(); @@ -125,7 +128,9 @@ private: VideoBackend* m_backend = nullptr; QSize m_size; MessagePainter* m_messagePainter = nullptr; - QElapsedTimer m_delayTimer; + QTimer m_swapTimer{this}; + bool m_needsUnlock = false; + bool m_frameReady = false; VideoProxy* m_videoProxy; }; From bbf06b5f1312c7f5d8dcf3a4e6f8c9ddbf76e6d8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 23 May 2019 13:09:13 -0700 Subject: [PATCH 62/63] GBA Video: Simplify GL sprite flipping --- src/gba/renderers/gl.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 4fb3740a9..73dbeabec 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -1443,10 +1443,6 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB spriteY -= 256; } - if (!GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesBIsVFlip(sprite->b)) { - spriteY = (renderer->firstY - height) + (renderer->firstY - spriteY) + 1; - } - int totalWidth = width; int totalHeight = height; if (GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesAIsDoubleSize(sprite->a)) { @@ -1466,7 +1462,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); glUniform2i(uniforms[GBA_GL_VS_LOC], totalHeight, 0); - glUniform2i(uniforms[GBA_GL_VS_MAXPOS], (GBAObjAttributesBIsHFlip(sprite->b) && !GBAObjAttributesAIsTransformed(sprite->a)) ? -totalWidth : totalWidth, totalHeight); + glUniform2i(uniforms[GBA_GL_VS_MAXPOS], totalWidth, totalHeight); glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0); glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1); glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase); @@ -1484,7 +1480,15 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { mat.a / 256.f, mat.c / 256.f, mat.b / 256.f, mat.d / 256.f }); } else { - glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { 1.f, 0, 0, 1.f }); + int flipX = 1; + int flipY = 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + flipX = -1; + } + if (GBAObjAttributesBIsVFlip(sprite->b)) { + flipY = -1; + } + glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { flipX, 0, 0, flipY }); } glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) { From 2506fd5330dbe920836ac442e1b0614a3efc8854 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 23 May 2019 13:11:53 -0700 Subject: [PATCH 63/63] GBA Video: Detect OpenGL ES --- src/gba/renderers/gl.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 73dbeabec..a9721b30d 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -54,6 +54,9 @@ struct GBAVideoGLUniform { int type; }; +static const GLchar* const _gles3Header = + "#version 300\n"; + static const GLchar* const _gl3Header = "#version 130\n"; @@ -751,8 +754,13 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { } char log[1024]; - const GLchar* shaderBuffer[8]; - shaderBuffer[0] = _gl3Header; + const GLchar* shaderBuffer[4]; + const GLubyte* version = glGetString(GL_VERSION); + if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) { + shaderBuffer[0] = _gl3Header; + } else { + shaderBuffer[0] = _gles3Header; + } GLuint vs = glCreateShader(GL_VERTEX_SHADER); shaderBuffer[1] = _vertexShader;