aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Bouclet2018-08-23 07:19:12 +0200
committerBastien Bouclet2018-08-26 21:08:40 +0200
commit3376597abddd56f35888d561462df9a0ca662bcc (patch)
tree5e89e9d9056f884e0c7b7fb9cf069f2e640237ac
parent81f78d4ddf2bee6164dd9cb90657d0666688603d (diff)
downloadscummvm-rg350-3376597abddd56f35888d561462df9a0ca662bcc.tar.gz
scummvm-rg350-3376597abddd56f35888d561462df9a0ca662bcc.tar.bz2
scummvm-rg350-3376597abddd56f35888d561462df9a0ca662bcc.zip
OPENGL: Use premultiplied alpha for color-keyed cursors
This fixes colour fringing on keyed cursors when using filtering. Fixes Trac#10594.
-rw-r--r--backends/graphics/opengl/framebuffer.cpp22
-rw-r--r--backends/graphics/opengl/framebuffer.h27
-rw-r--r--backends/graphics/opengl/opengl-graphics.cpp76
-rw-r--r--backends/graphics/opengl/texture.cpp19
4 files changed, 101 insertions, 43 deletions
diff --git a/backends/graphics/opengl/framebuffer.cpp b/backends/graphics/opengl/framebuffer.cpp
index 7191aab8bc..671c448d26 100644
--- a/backends/graphics/opengl/framebuffer.cpp
+++ b/backends/graphics/opengl/framebuffer.cpp
@@ -28,7 +28,7 @@ namespace OpenGL {
Framebuffer::Framebuffer()
: _viewport(), _projectionMatrix(), _isActive(false), _clearColor(),
- _blendState(false), _scissorTestState(false), _scissorBox() {
+ _blendState(kBlendModeDisabled), _scissorTestState(false), _scissorBox() {
}
void Framebuffer::activate() {
@@ -62,8 +62,8 @@ void Framebuffer::setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {
}
}
-void Framebuffer::enableBlend(bool enable) {
- _blendState = enable;
+void Framebuffer::enableBlend(BlendMode mode) {
+ _blendState = mode;
// Directly apply changes when we are active.
if (isActive()) {
@@ -105,10 +105,18 @@ void Framebuffer::applyClearColor() {
}
void Framebuffer::applyBlendState() {
- if (_blendState) {
- GL_CALL(glEnable(GL_BLEND));
- } else {
- GL_CALL(glDisable(GL_BLEND));
+ switch (_blendState) {
+ case kBlendModeDisabled:
+ GL_CALL(glDisable(GL_BLEND));
+ break;
+ case kBlendModeTraditionalTransparency:
+ GL_CALL(glEnable(GL_BLEND));
+ GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+ break;
+ case kBlendModePremultipliedTransparency:
+ GL_CALL(glEnable(GL_BLEND));
+ GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ break;
}
}
diff --git a/backends/graphics/opengl/framebuffer.h b/backends/graphics/opengl/framebuffer.h
index c44c98ddc4..2f0245faa7 100644
--- a/backends/graphics/opengl/framebuffer.h
+++ b/backends/graphics/opengl/framebuffer.h
@@ -37,6 +37,29 @@ public:
virtual ~Framebuffer() {};
public:
+ enum BlendMode {
+ /**
+ * Newly drawn pixels overwrite the existing contents of the framebuffer
+ * without mixing with them
+ */
+ kBlendModeDisabled,
+
+ /**
+ * Newly drawn pixels mix with the framebuffer based on their alpha value
+ * for transparency.
+ */
+ kBlendModeTraditionalTransparency,
+
+ /**
+ * Newly drawn pixels mix with the framebuffer based on their alpha value
+ * for transparency.
+ *
+ * Requires the image data being drawn to have its color values pre-multipled
+ * with the alpha value.
+ */
+ kBlendModePremultipliedTransparency
+ };
+
/**
* Set the clear color of the framebuffer.
*/
@@ -45,7 +68,7 @@ public:
/**
* Enable/disable GL_BLEND.
*/
- void enableBlend(bool enable);
+ void enableBlend(BlendMode mode);
/**
* Enable/disable GL_SCISSOR_TEST.
@@ -102,7 +125,7 @@ private:
GLfloat _clearColor[4];
void applyClearColor();
- bool _blendState;
+ BlendMode _blendState;
void applyBlendState();
bool _scissorTestState;
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index 7d29a81f12..cfddc93e2f 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -420,16 +420,22 @@ void OpenGLGraphicsManager::updateScreen() {
const GLfloat shakeOffset = _gameScreenShakeOffset * (GLfloat)_gameDrawRect.height() / _gameScreen->getHeight();
+ // Alpha blending is disabled when drawing the screen
+ _backBuffer.enableBlend(Framebuffer::kBlendModeDisabled);
+
// First step: Draw the (virtual) game screen.
g_context.getActivePipeline()->drawTexture(_gameScreen->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top + shakeOffset, _gameDrawRect.width(), _gameDrawRect.height());
// Second step: Draw the overlay if visible.
if (_overlayVisible) {
+ _backBuffer.enableBlend(Framebuffer::kBlendModeTraditionalTransparency);
g_context.getActivePipeline()->drawTexture(_overlay->getGLTexture(), 0, 0, _overlayDrawRect.width(), _overlayDrawRect.height());
}
// Third step: Draw the cursor if visible.
if (_cursorVisible && _cursor) {
+ _backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency);
+
// Adjust game screen shake position, but only when the overlay is not
// visible.
const GLfloat cursorOffset = _overlayVisible ? 0 : shakeOffset;
@@ -446,6 +452,10 @@ void OpenGLGraphicsManager::updateScreen() {
#ifdef USE_OSD
// Fourth step: Draw the OSD.
+ if (_osdMessageSurface || _osdIconSurface) {
+ _backBuffer.enableBlend(Framebuffer::kBlendModeTraditionalTransparency);
+ }
+
if (_osdMessageSurface) {
// Update alpha value.
const int diff = g_system->getMillis(false) - _osdMessageFadeStartTime;
@@ -549,20 +559,35 @@ void OpenGLGraphicsManager::grabOverlay(void *buf, int pitch) const {
}
namespace {
-template<typename DstPixel, typename SrcPixel>
-void applyColorKey(DstPixel *dst, const SrcPixel *src, uint w, uint h, uint dstPitch, uint srcPitch, SrcPixel keyColor, DstPixel alphaMask) {
- const uint srcAdd = srcPitch - w * sizeof(SrcPixel);
- const uint dstAdd = dstPitch - w * sizeof(DstPixel);
-
- while (h-- > 0) {
- for (uint x = w; x > 0; --x, ++dst, ++src) {
- if (*src == keyColor) {
- *dst &= ~alphaMask;
+template<typename SrcColor, typename DstColor>
+void multiplyColorWithAlpha(const byte *src, byte *dst, const uint w, const uint h,
+ const Graphics::PixelFormat &srcFmt, const Graphics::PixelFormat &dstFmt,
+ const uint srcPitch, const uint dstPitch, const SrcColor keyColor) {
+ for (uint y = 0; y < h; ++y) {
+ for (uint x = 0; x < w; ++x) {
+ const uint32 color = *(const SrcColor *)src;
+
+ if (color == keyColor) {
+ *(DstColor *)dst = 0;
+ } else {
+ byte a, r, g, b;
+ srcFmt.colorToARGB(color, a, r, g, b);
+
+ if (a != 0xFF) {
+ r = (int) r * a / 255;
+ g = (int) g * a / 255;
+ b = (int) b * a / 255;
+ }
+
+ *(DstColor *)dst = dstFmt.ARGBToColor(a, r, g, b);
}
+
+ src += sizeof(SrcColor);
+ dst += sizeof(DstColor);
}
- dst = (DstPixel *)((byte *)dst + dstAdd);
- src = (const SrcPixel *)((const byte *)src + srcAdd);
+ src += srcPitch - w * srcFmt.bytesPerPixel;
+ dst += dstPitch - w * dstFmt.bytesPerPixel;
}
}
} // End of anonymous namespace
@@ -629,27 +654,26 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
// Copy the cursor data to the actual texture surface. This will make
// sure that the data is also converted to the expected format.
- Graphics::crossBlit((byte *)dst->getPixels(), (const byte *)buf, dst->pitch, srcPitch,
- w, h, dst->format, inputFormat);
- // We apply the color key by setting the alpha bits of the pixels to
- // fully transparent.
- const uint32 aMask = (0xFF >> dst->format.aLoss) << dst->format.aShift;
+ // Also multiply the color values with the alpha channel.
+ // The pre-multiplication allows using a blend mode that prevents
+ // color fringes due to filtering.
+
if (dst->format.bytesPerPixel == 2) {
if (inputFormat.bytesPerPixel == 2) {
- applyColorKey<uint16, uint16>((uint16 *)dst->getPixels(), (const uint16 *)buf, w, h,
- dst->pitch, srcPitch, keycolor, aMask);
+ multiplyColorWithAlpha<uint16, uint16>((const byte *) buf, (byte *) dst->getPixels(), w, h,
+ inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
} else if (inputFormat.bytesPerPixel == 4) {
- applyColorKey<uint16, uint32>((uint16 *)dst->getPixels(), (const uint32 *)buf, w, h,
- dst->pitch, srcPitch, keycolor, aMask);
+ multiplyColorWithAlpha<uint32, uint16>((const byte *) buf, (byte *) dst->getPixels(), w, h,
+ inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
}
} else {
if (inputFormat.bytesPerPixel == 2) {
- applyColorKey<uint32, uint16>((uint32 *)dst->getPixels(), (const uint16 *)buf, w, h,
- dst->pitch, srcPitch, keycolor, aMask);
+ multiplyColorWithAlpha<uint16, uint32>((const byte *) buf, (byte *) dst->getPixels(), w, h,
+ inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
} else if (inputFormat.bytesPerPixel == 4) {
- applyColorKey<uint32, uint32>((uint32 *)dst->getPixels(), (const uint32 *)buf, w, h,
- dst->pitch, srcPitch, keycolor, aMask);
+ multiplyColorWithAlpha<uint32, uint32>((const byte *) buf, (byte *) dst->getPixels(), w, h,
+ inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
}
}
@@ -881,14 +905,10 @@ void OpenGLGraphicsManager::notifyContextCreate(const Graphics::PixelFormat &def
g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, 1.0f);
- GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
-
// Setup backbuffer state.
// Default to black as clear color.
_backBuffer.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- // Setup alpha blend (for overlay and cursor).
- _backBuffer.enableBlend(true);
g_context.getActivePipeline()->setFramebuffer(&_backBuffer);
diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp
index 33598b5488..2a078534ab 100644
--- a/backends/graphics/opengl/texture.cpp
+++ b/backends/graphics/opengl/texture.cpp
@@ -344,16 +344,16 @@ Graphics::PixelFormat TextureCLUT8::getFormat() const {
}
void TextureCLUT8::setColorKey(uint colorKey) {
- // We remove all alpha bits from the palette entry of the color key.
- // This makes sure its properly handled as color key.
- const uint32 aMask = (0xFF >> _format.aLoss) << _format.aShift;
-
+ // The key color is set to black so the color value is pre-multiplied with the alpha value
+ // to avoid color fringes due to filtering.
+ // Erasing the color data is not a problem as the palette is always fully re-initialized
+ // before setting the key color.
if (_format.bytesPerPixel == 2) {
uint16 *palette = (uint16 *)_palette + colorKey;
- *palette &= ~aMask;
+ *palette = 0;
} else if (_format.bytesPerPixel == 4) {
uint32 *palette = (uint32 *)_palette + colorKey;
- *palette &= ~aMask;
+ *palette = 0;
} else {
warning("TextureCLUT8::setColorKey: Unsupported pixel depth %d", _format.bytesPerPixel);
}
@@ -581,6 +581,13 @@ Graphics::PixelFormat TextureCLUT8GPU::getFormat() const {
}
void TextureCLUT8GPU::setColorKey(uint colorKey) {
+ // The key color is set to black so the color value is pre-multiplied with the alpha value
+ // to avoid color fringes due to filtering.
+ // Erasing the color data is not a problem as the palette is always fully re-initialized
+ // before setting the key color.
+ _palette[colorKey * 4 ] = 0x00;
+ _palette[colorKey * 4 + 1] = 0x00;
+ _palette[colorKey * 4 + 2] = 0x00;
_palette[colorKey * 4 + 3] = 0x00;
_paletteDirty = true;