OpenGL, Qt: Add interframe blending
jump to
@@ -10,6 +10,7 @@ - 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 + - Interframe blending for games that use flicker effects Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs
@@ -42,6 +42,7 @@ int width;
int height; bool lockAspectRatio; bool lockIntegerScaling; + bool interframeBlending; bool resampleVideo; bool suspendScreensaver; char* shader;
@@ -354,6 +354,9 @@ }
if (_lookupIntValue(config, "lockIntegerScaling", &fakeBool)) { opts->lockIntegerScaling = fakeBool; } + if (_lookupIntValue(config, "interframeBlending", &fakeBool)) { + opts->interframeBlending = fakeBool; + } if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { opts->resampleVideo = fakeBool; }
@@ -24,13 +24,20 @@
static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; - glGenTextures(1, &context->tex); - glBindTexture(GL_TEXTURE_2D, context->tex); + glGenTextures(2, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[0]); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); #ifndef _WIN32 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #endif + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +#ifndef _WIN32 + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif + context->activeTex = 0; } static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) {@@ -38,7 +45,20 @@ struct mGLContext* context = (struct mGLContext*) v;
v->width = width; v->height = height; - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[0]); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif + + glBindTexture(GL_TEXTURE_2D, context->tex[1]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);@@ -54,7 +74,7 @@ }
static void mGLContextDeinit(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; - glDeleteTextures(1, &context->tex); + glDeleteTextures(2, context->tex); } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) {@@ -96,7 +116,21 @@ glLoadIdentity();
glOrtho(0, v->width, v->height, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glBindTexture(GL_TEXTURE_2D, context->tex); + if (v->interframeBlending) { + glEnable(GL_BLEND); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); + if (v->filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); if (v->filter) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);@@ -105,11 +139,13 @@ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDisable(GL_BLEND); } void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { struct mGLContext* context = (struct mGLContext*) v; - glBindTexture(GL_TEXTURE_2D, context->tex); + context->activeTex ^= 1; + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
@@ -26,7 +26,8 @@
struct mGLContext { struct VideoBackend d; - GLuint tex; + GLuint tex[2]; + int activeTex; }; void mGLContextCreate(struct mGLContext*);
@@ -69,6 +69,16 @@ " color.a = 1.;\n"
" gl_FragColor = color;\n" "}"; +static const char* const _interframeFragmentShader = + "varying vec2 texCoord;\n" + "uniform sampler2D tex;\n" + + "void main() {\n" + " vec4 color = texture2D(tex, texCoord);\n" + " color.a = 0.5;\n" + " gl_FragColor = color;\n" + "}"; + static const GLfloat _vertices[] = { -1.f, -1.f, -1.f, 1.f,@@ -133,16 +143,15 @@ uniforms[3].max.fvec3[1] = 1.0f;
uniforms[3].max.fvec3[2] = 1.0f; mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4); mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0); + mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, 0, 0); if (context->initialShader.vao != (GLuint) -1) { 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(context->interframeShader.vao); + glBindBuffer(GL_ARRAY_BUFFER, context->vbo); glBindVertexArray(0); }@@ -177,6 +186,7 @@ glDeleteTextures(1, &context->tex);
glDeleteBuffers(1, &context->vbo); mGLES2ShaderDeinit(&context->initialShader); mGLES2ShaderDeinit(&context->finalShader); + mGLES2ShaderDeinit(&context->interframeShader); free(context->initialShader.uniforms); }@@ -327,6 +337,11 @@ glGetIntegerv(GL_VIEWPORT, viewport);
context->finalShader.filter = v->filter; _drawShader(context, &context->initialShader); + if (v->interframeBlending) { + context->interframeShader.blend = true; + glViewport(0, 0, viewport[2], viewport[3]); + _drawShader(context, &context->interframeShader); + } size_t n; for (n = 0; n < context->nShaders; ++n) { glViewport(0, 0, viewport[2], viewport[3]);@@ -334,6 +349,13 @@ _drawShader(context, &context->shaders[n]);
} glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _drawShader(context, &context->finalShader); + if (v->interframeBlending) { + context->interframeShader.blend = false; + glBindTexture(GL_TEXTURE_2D, context->tex); + _drawShader(context, &context->initialShader); + glViewport(0, 0, viewport[2], viewport[3]); + _drawShader(context, &context->interframeShader); + } glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); if (context->finalShader.vao != (GLuint) -1) {@@ -391,6 +413,8 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (shader->width > 0 && shader->height > 0) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, shader->width, shader->height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); } glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, shader->tex, 0);@@ -448,6 +472,10 @@
const GLubyte* extensions = glGetString(GL_EXTENSIONS); if (shaderBuffer[0] == _gles2Header || version[0] >= '3' || (extensions && strstr((const char*) extensions, "_vertex_array_object") != NULL)) { glGenVertexArrays(1, &shader->vao); + glBindVertexArray(shader->vao); + glEnableVertexAttribArray(shader->positionLocation); + glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glBindVertexArray(0); } else { shader->vao = -1; }
@@ -82,6 +82,7 @@ GLuint vbo;
struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; + struct mGLES2Shader interframeShader; struct mGLES2Shader* shaders; size_t nShaders;
@@ -119,6 +119,7 @@ m_opts.rewindBufferCapacity = 300;
m_opts.useBios = true; m_opts.suspendScreensaver = true; m_opts.lockAspectRatio = true; + m_opts.interframeBlending = false; mCoreConfigLoad(&m_config); mCoreConfigLoadDefaults(&m_config, &m_opts); mCoreConfigSetDefaultIntValue(&m_config, "sgb.borders", 1);
@@ -70,6 +70,10 @@ void Display::lockIntegerScaling(bool lock) {
m_lockIntegerScaling = lock; } +void Display::interframeBlending(bool lock) { + m_interframeBlending = lock; +} + void Display::filter(bool filter) { m_filter = filter; }
@@ -42,6 +42,7 @@ static void setDriver(Driver driver) { s_driver = driver; }
bool isAspectRatioLocked() const { return m_lockAspectRatio; } bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } + bool hasInterframeBlending() const { return m_interframeBlending; } bool isFiltered() const { return m_filter; } virtual void startDrawing(std::shared_ptr<CoreController>) = 0;@@ -62,6 +63,7 @@ virtual void unpauseDrawing() = 0;
virtual void forceDraw() = 0; virtual void lockAspectRatio(bool lock); virtual void lockIntegerScaling(bool lock); + virtual void interframeBlending(bool enable); virtual void filter(bool filter); virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0;@@ -83,6 +85,7 @@
MessagePainter m_messagePainter; bool m_lockAspectRatio = false; bool m_lockIntegerScaling = false; + bool m_interframeBlending = false; bool m_filter = false; QTimer m_mouseTimer; };
@@ -102,6 +102,7 @@ m_drawThread->start();
lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); + interframeBlending(hasInterframeBlending()); filter(isFiltered()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());@@ -161,6 +162,13 @@ void DisplayGL::lockIntegerScaling(bool lock) {
Display::lockIntegerScaling(lock); if (m_drawThread) { QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock)); + } +} + +void DisplayGL::interframeBlending(bool enable) { + Display::interframeBlending(enable); + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "interframeBlending", Q_ARG(bool, enable)); } }@@ -276,6 +284,7 @@
m_backend->user = this; m_backend->filter = false; m_backend->lockAspectRatio = false; + m_backend->interframeBlending = false; for (int i = 0; i < 2; ++i) { m_free.append(new uint32_t[1024 * 2048]);@@ -342,6 +351,10 @@
void PainterGL::lockIntegerScaling(bool lock) { m_backend->lockIntegerScaling = lock; resize(m_size); +} + +void PainterGL::interframeBlending(bool enable) { + m_backend->interframeBlending = enable; } void PainterGL::filter(bool filter) {@@ -546,7 +559,7 @@ }
#endif #ifdef BUILD_GL mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend); - return glBackend->tex; + return glBackend->tex[0]; #else return -1; #endif
@@ -52,6 +52,7 @@ void unpauseDrawing() override;
void forceDraw() override; void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; + void interframeBlending(bool enable) override; void filter(bool filter) override; void framePosted() override; void setShaders(struct VDir*) override;@@ -96,6 +97,7 @@ void unpause();
void resize(const QSize& size); void lockAspectRatio(bool lock); void lockIntegerScaling(bool lock); + void interframeBlending(bool enable); void filter(bool filter); void resizeContext();
@@ -25,6 +25,7 @@ QSize size = controller->screenDimensions();
m_width = size.width(); m_height = size.height(); m_backing = std::move(QImage()); + m_oldBacking = std::move(QImage()); m_isDrawing = true; m_context = controller; }@@ -41,6 +42,11 @@ }
void DisplayQt::lockIntegerScaling(bool lock) { Display::lockIntegerScaling(lock); + update(); +} + +void DisplayQt::interframeBlending(bool lock) { + Display::interframeBlending(lock); update(); }@@ -55,6 +61,7 @@ const color_t* buffer = m_context->drawContext();
if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) { return; } + m_oldBacking = m_backing; #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB16);@@ -65,6 +72,9 @@ #else
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_ARGB32); m_backing = m_backing.convertToFormat(QImage::Format_RGB32); #endif +#ifndef COLOR_5_6_5 + m_backing = m_backing.rgbSwapped(); +#endif } void DisplayQt::resizeContext() {@@ -75,6 +85,7 @@ QSize size = m_context->screenDimensions();
if (m_width != size.width() || m_height != size.height()) { m_width = size.width(); m_height = size.height(); + m_oldBacking = std::move(QImage()); m_backing = std::move(QImage()); } }@@ -98,13 +109,15 @@ if (isIntegerScalingLocked()) {
ds.setWidth(ds.width() - ds.width() % m_width); ds.setHeight(ds.height() - ds.height() % m_height); } +#warning TODO: Add interframeBlending QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds); -#ifdef COLOR_5_6_5 + if (hasInterframeBlending()) { + painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height)); + painter.setOpacity(0.5); + } painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height)); -#else - painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, m_width, m_height)); -#endif + painter.setOpacity(1); messagePainter()->paint(&painter); }
@@ -30,6 +30,7 @@ void unpauseDrawing() override { m_isDrawing = true; }
void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; + void interframeBlending(bool enable) override; void filter(bool filter) override; void framePosted() override; void setShaders(struct VDir*) override {}@@ -44,6 +45,7 @@ bool m_isDrawing = false;
unsigned m_width; unsigned m_height; QImage m_backing{nullptr}; + QImage m_oldBacking{nullptr}; std::shared_ptr<CoreController> m_context = nullptr; };
@@ -377,6 +377,7 @@ saveSetting("frameskip", m_ui.frameskip);
saveSetting("autofireThreshold", m_ui.autofireThreshold); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); + saveSetting("interframeBlending", m_ui.interframeBlending); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); saveSetting("fastForwardVolume", m_ui.volumeFf);@@ -534,6 +535,7 @@ loadSetting("fpsTarget", m_ui.fpsTarget);
loadSetting("autofireThreshold", m_ui.autofireThreshold); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling); + loadSetting("interframeBlending", m_ui.interframeBlending); loadSetting("volume", m_ui.volume, 0x100); loadSetting("mute", m_ui.mute, false); loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value());
@@ -7,7 +7,7 @@ <rect>
<x>0</x> <y>0</y> <width>849</width> - <height>753</height> + <height>797</height> </rect> </property> <property name="sizePolicy">@@ -445,7 +445,7 @@ <string>Force integer scaling</string>
</property> </widget> </item> - <item row="14" column="1"> + <item row="15" column="1"> <widget class="QCheckBox" name="resampleVideo"> <property name="text"> <string>Bilinear filtering</string>@@ -456,6 +456,13 @@ <item row="9" column="1">
<widget class="QPushButton" name="nativeGB"> <property name="text"> <string>Native (59.7275)</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QCheckBox" name="interframeBlending"> + <property name="text"> + <string>Interframe blending</string> </property> </widget> </item>
@@ -719,6 +719,7 @@ QSize size = m_controller->screenDimensions();
m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling"); m_config->updateOption("lockAspectRatio"); + m_config->updateOption("interframeBlending"); if (m_savedScale > 0) { resizeFrame(size * m_savedScale); }@@ -883,6 +884,7 @@ });
const mCoreOptions* opts = m_config->options(); m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->interframeBlending(opts->interframeBlending); m_display->filter(opts->resampleVideo); #if defined(BUILD_GL) || defined(BUILD_GLES2) if (opts->shader) {@@ -1339,6 +1341,15 @@ m_screenWidget->setLockIntegerScaling(value.toBool());
} }, this); m_config->updateOption("lockIntegerScaling"); + + ConfigOption* interframeBlending = m_config->addOption("interframeBlending"); + interframeBlending->addBoolean(tr("Interframe blending"), &m_actions, "av"); + interframeBlending->connect([this](const QVariant& value) { + if (m_display) { + m_display->interframeBlending(value.toBool()); + } + }, this); + m_config->updateOption("interframeBlending"); ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Bilinear filtering"), &m_actions, "av");
@@ -35,6 +35,7 @@ mGLContextCreate(&renderer->gl);
renderer->gl.d.user = renderer; renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; renderer->gl.d.lockIntegerScaling = renderer->lockIntegerScaling; + renderer->gl.d.interframeBlending = renderer->interframeBlending; renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0);
@@ -136,6 +136,7 @@ renderer.player.windowUpdated = 0;
renderer.lockAspectRatio = renderer.core->opts.lockAspectRatio; renderer.lockIntegerScaling = renderer.core->opts.lockIntegerScaling; + renderer.interframeBlending = renderer.core->opts.interframeBlending; renderer.filter = renderer.core->opts.resampleVideo; if (!mSDLInit(&renderer)) {
@@ -64,6 +64,7 @@ int ratio;
bool lockAspectRatio; bool lockIntegerScaling; + bool interframeBlending; bool filter; #ifdef BUILD_GL
@@ -36,6 +36,7 @@
bool filter; bool lockAspectRatio; bool lockIntegerScaling; + bool interframeBlending; }; struct VideoShader {