diff options
Diffstat (limited to 'src/opengl/qglpixmapfilter.cpp')
-rw-r--r-- | src/opengl/qglpixmapfilter.cpp | 779 |
1 files changed, 596 insertions, 183 deletions
diff --git a/src/opengl/qglpixmapfilter.cpp b/src/opengl/qglpixmapfilter.cpp index 060336975a..0eaab28b6d 100644 --- a/src/opengl/qglpixmapfilter.cpp +++ b/src/opengl/qglpixmapfilter.cpp @@ -43,6 +43,8 @@ #include "private/qpixmapdata_gl_p.h" #include "private/qpaintengineex_opengl2_p.h" #include "private/qglengineshadermanager_p.h" +#include "private/qpixmapdata_p.h" +#include "private/qimagepixmapcleanuphooks_p.h" #include "qglpixmapfilter_p.h" #include "qgraphicssystem_gl_p.h" #include "qpaintengine_opengl_p.h" @@ -54,7 +56,7 @@ #include "private/qapplication_p.h" #include "private/qmath_p.h" - +#include "qmath.h" QT_BEGIN_NAMESPACE @@ -100,11 +102,11 @@ private: class QGLPixmapBlurFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapBlurFilter> { public: - QGLPixmapBlurFilter(Qt::RenderHint hint); + QGLPixmapBlurFilter(QGraphicsBlurEffect::BlurHint hint); void setUniforms(QGLShaderProgram *program); - static QByteArray generateGaussianShader(int radius, bool dropShadow = false); + static QByteArray generateGaussianShader(int radius, bool singlePass = false, bool dropShadow = false); protected: bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; @@ -113,16 +115,21 @@ private: mutable QSize m_textureSize; mutable bool m_horizontalBlur; + mutable bool m_singlePass; + mutable bool m_animatedBlur; + + mutable qreal m_t; + mutable QSize m_targetSize; mutable bool m_haveCached; mutable int m_cachedRadius; - mutable Qt::RenderHint m_hint; + mutable QGraphicsBlurEffect::BlurHint m_hint; }; class QGLPixmapDropShadowFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapDropShadowFilter> { public: - QGLPixmapDropShadowFilter(Qt::RenderHint hint); + QGLPixmapDropShadowFilter(QGraphicsBlurEffect::BlurHint hint); void setUniforms(QGLShaderProgram *program); @@ -132,10 +139,11 @@ protected: private: mutable QSize m_textureSize; mutable bool m_horizontalBlur; + mutable bool m_singlePass; mutable bool m_haveCached; mutable int m_cachedRadius; - mutable Qt::RenderHint m_hint; + mutable QGraphicsBlurEffect::BlurHint m_hint; }; extern QGLWidget *qt_gl_share_widget(); @@ -151,13 +159,18 @@ QPixmapFilter *QGL2PaintEngineEx::pixmapFilter(int type, const QPixmapFilter *pr case QPixmapFilter::BlurFilter: { const QPixmapBlurFilter *proto = static_cast<const QPixmapBlurFilter *>(prototype); - if (proto->blurHint() == Qt::PerformanceHint || proto->radius() <= 5) { + if (proto->blurHint() == QGraphicsBlurEffect::AnimationHint) { + if (!d->animationBlurFilter) + d->animationBlurFilter.reset(new QGLPixmapBlurFilter(proto->blurHint())); + return d->animationBlurFilter.data(); + } + if (proto->blurHint() == QGraphicsBlurEffect::PerformanceHint || proto->radius() <= 5) { if (!d->fastBlurFilter) - d->fastBlurFilter.reset(new QGLPixmapBlurFilter(Qt::PerformanceHint)); + d->fastBlurFilter.reset(new QGLPixmapBlurFilter(QGraphicsBlurEffect::PerformanceHint)); return d->fastBlurFilter.data(); } if (!d->blurFilter) - d->blurFilter.reset(new QGLPixmapBlurFilter(Qt::QualityHint)); + d->blurFilter.reset(new QGLPixmapBlurFilter(QGraphicsBlurEffect::QualityHint)); return d->blurFilter.data(); } @@ -165,11 +178,11 @@ QPixmapFilter *QGL2PaintEngineEx::pixmapFilter(int type, const QPixmapFilter *pr const QPixmapDropShadowFilter *proto = static_cast<const QPixmapDropShadowFilter *>(prototype); if (proto->blurRadius() <= 5) { if (!d->fastDropShadowFilter) - d->fastDropShadowFilter.reset(new QGLPixmapDropShadowFilter(Qt::PerformanceHint)); + d->fastDropShadowFilter.reset(new QGLPixmapDropShadowFilter(QGraphicsBlurEffect::PerformanceHint)); return d->fastDropShadowFilter.data(); } if (!d->dropShadowFilter) - d->dropShadowFilter.reset(new QGLPixmapDropShadowFilter(Qt::QualityHint)); + d->dropShadowFilter.reset(new QGLPixmapDropShadowFilter(QGraphicsBlurEffect::QualityHint)); return d->dropShadowFilter.data(); } @@ -298,123 +311,470 @@ bool QGLPixmapConvolutionFilter::processGL(QPainter *painter, const QPointF &pos return true; } -static const char *qt_gl_blur_filter_fast = - "const int samples = 9;" - "uniform mediump vec2 delta;" - "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords) {" - " mediump vec4 color = vec4(0.0, 0.0, 0.0, 0.0);" - " mediump float offset = (float(samples) - 1.0) / 2.0;" - " for (int i = 0; i < samples; i++) {" - " mediump vec2 coord = srcCoords + delta * (offset - float(i)) / offset;" - " color += texture2D(src, coord);" - " }" - " return color * (1.0 / float(samples));" - "}"; - -static const char *qt_gl_drop_shadow_filter_fast = - "const int samples = 9;" - "uniform mediump vec2 delta;" - "uniform mediump vec4 shadowColor;" - "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords) {" - " mediump vec4 color = vec4(0.0, 0.0, 0.0, 0.0);" - " mediump float offset = (float(samples) - 1.0) / 2.0;" - " for (int i = 0; i < samples; i++) {" - " mediump vec2 coord = srcCoords + delta * (offset - float(i)) / offset;" - " color += texture2D(src, coord).a * shadowColor;" - " }" - " return color * (1.0 / float(samples));" - "}"; - -QGLPixmapBlurFilter::QGLPixmapBlurFilter(Qt::RenderHint hint) - : m_haveCached(false) - , m_cachedRadius(5) +static const char *qt_gl_texture_sampling_helper = + "lowp float texture2DAlpha(lowp sampler2D src, highp vec2 srcCoords) {\n" + " return texture2D(src, srcCoords).a;\n" + "}\n"; + +QGLPixmapBlurFilter::QGLPixmapBlurFilter(QGraphicsBlurEffect::BlurHint hint) + : m_animatedBlur(false) + , m_haveCached(false) + , m_cachedRadius(0) , m_hint(hint) { - if (hint == Qt::PerformanceHint) { - QGLPixmapBlurFilter *filter = const_cast<QGLPixmapBlurFilter *>(this); - filter->setSource(qt_gl_blur_filter_fast); - m_haveCached = true; +} + +// should be even numbers as they will be divided by two +static const int qCachedBlurLevels[] = { 6, 14, 30 }; +static const int qNumCachedBlurTextures = sizeof(qCachedBlurLevels) / sizeof(*qCachedBlurLevels); +static const int qMaxCachedBlurLevel = qCachedBlurLevels[qNumCachedBlurTextures - 1]; + +static qreal qLogBlurLevel(int level) +{ + static bool initialized = false; + static qreal logBlurLevelCache[qNumCachedBlurTextures]; + if (!initialized) { + for (int i = 0; i < qNumCachedBlurTextures; ++i) + logBlurLevelCache[i] = qLn(qCachedBlurLevels[i]); + initialized = true; } + return logBlurLevelCache[level]; } -bool QGLPixmapBlurFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const +class QGLBlurTextureInfo { - QGLPixmapBlurFilter *filter = const_cast<QGLPixmapBlurFilter *>(this); +public: + QGLBlurTextureInfo(QSize size, GLuint textureIds[]) + : m_size(size) + { + for (int i = 0; i < qNumCachedBlurTextures; ++i) + m_textureIds[i] = textureIds[i]; + } - int radius = this->radius(); - if (!m_haveCached || (m_hint == Qt::QualityHint && radius != m_cachedRadius)) { - // Only regenerate the shader from source if parameters have changed. - m_haveCached = true; - m_cachedRadius = radius; - filter->setSource(generateGaussianShader(radius)); + ~QGLBlurTextureInfo() + { + glDeleteTextures(qNumCachedBlurTextures, m_textureIds); } - QGLFramebufferObjectFormat format; - format.setInternalTextureFormat(GLenum(src.hasAlphaChannel() ? GL_RGBA : GL_RGB)); - QGLFramebufferObject *fbo = qgl_fbo_pool()->acquire(src.size(), format); + QSize size() const { return m_size; } + GLuint textureId(int i) const { return m_textureIds[i]; } + +private: + GLuint m_textureIds[qNumCachedBlurTextures]; + QSize m_size; +}; + +class QGLBlurTextureCache : public QObject +{ +public: + static QGLBlurTextureCache *cacheForContext(const QGLContext *context); + + QGLBlurTextureCache(); + ~QGLBlurTextureCache(); - if (!fbo) - return false; + QGLBlurTextureInfo *takeBlurTextureInfo(const QPixmap &pixmap); + bool fitsInCache(const QPixmap &pixmap) const; + bool hasBlurTextureInfo(const QPixmap &pixmap) const; + void insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info); + void clearBlurTextureInfo(const QPixmap &pixmap); - glBindTexture(GL_TEXTURE_2D, fbo->texture()); + void timerEvent(QTimerEvent *event); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +private: + static void pixmapDestroyed(QPixmap *pixmap); + + QCache<quint64, QGLBlurTextureInfo > cache; + + static QList<QGLBlurTextureCache *> blurTextureCaches; + + int timerId; +}; + +QList<QGLBlurTextureCache *> QGLBlurTextureCache::blurTextureCaches; + +static void QGLBlurTextureCache_free(void *ptr) +{ + delete reinterpret_cast<QGLBlurTextureCache *>(ptr); +} + +Q_GLOBAL_STATIC_WITH_ARGS(QGLContextResource, qt_blur_texture_caches, (QGLBlurTextureCache_free)) + +QGLBlurTextureCache::QGLBlurTextureCache() + : timerId(0) +{ + cache.setMaxCost(4 * 1024 * 1024); + blurTextureCaches.append(this); +} + +QGLBlurTextureCache::~QGLBlurTextureCache() +{ + blurTextureCaches.removeAt(blurTextureCaches.indexOf(this)); +} + +void QGLBlurTextureCache::timerEvent(QTimerEvent *) +{ + killTimer(timerId); + timerId = 0; + + cache.clear(); +} + +QGLBlurTextureCache *QGLBlurTextureCache::cacheForContext(const QGLContext *context) +{ + QGLBlurTextureCache *p = reinterpret_cast<QGLBlurTextureCache *>(qt_blur_texture_caches()->value(context)); + if (!p) { + p = new QGLBlurTextureCache; + qt_blur_texture_caches()->insert(context, p); + } + return p; +} + +QGLBlurTextureInfo *QGLBlurTextureCache::takeBlurTextureInfo(const QPixmap &pixmap) +{ + return cache.take(pixmap.cacheKey()); +} + +void QGLBlurTextureCache::clearBlurTextureInfo(const QPixmap &pixmap) +{ + cache.remove(pixmap.cacheKey()); +} + +bool QGLBlurTextureCache::hasBlurTextureInfo(const QPixmap &pixmap) const +{ + return cache.contains(pixmap.cacheKey()); +} + +void QGLBlurTextureCache::insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info) +{ + static bool hookAdded = false; + if (!hookAdded) { + QImagePixmapCleanupHooks::instance()->addPixmapDestructionHook(pixmapDestroyed); + hookAdded = true; + } + + QImagePixmapCleanupHooks::enableCleanupHooks(pixmap); + cache.insert(pixmap.cacheKey(), info, pixmap.width() * pixmap.height()); + + if (timerId) + killTimer(timerId); + + timerId = startTimer(1000); +} + +bool QGLBlurTextureCache::fitsInCache(const QPixmap &pixmap) const +{ + return pixmap.width() * pixmap.height() <= cache.maxCost(); +} + +void QGLBlurTextureCache::pixmapDestroyed(QPixmap *pixmap) +{ + foreach (QGLBlurTextureCache *cache, blurTextureCaches) { + if (cache->hasBlurTextureInfo(*pixmap)) + cache->clearBlurTextureInfo(*pixmap); + } +} + +static const char *qt_gl_interpolate_filter = + "uniform lowp float interpolationValue;" + "uniform lowp sampler2D interpolateTarget;" + "uniform highp vec4 interpolateMapping;" + "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords)" + "{" + " return mix(texture2D(interpolateTarget, interpolateMapping.xy + interpolateMapping.zw * srcCoords)," + " texture2D(src, srcCoords), interpolationValue);" + "}"; + +static void initializeTexture(GLuint id, int width, int height) +{ + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); +} - // prepare for updateUniforms - m_textureSize = src.size(); +bool QGLPixmapBlurFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const +{ + QGLPixmapBlurFilter *filter = const_cast<QGLPixmapBlurFilter *>(this); - // horizontal pass, to pixmap - m_horizontalBlur = true; + QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); + QGLBlurTextureCache *blurTextureCache = QGLBlurTextureCache::cacheForContext(ctx); - QPainter fboPainter(fbo); + if (m_hint == QGraphicsBlurEffect::AnimationHint && blurTextureCache->fitsInCache(src)) { + QRect targetRect = src.rect().adjusted(-qMaxCachedBlurLevel, -qMaxCachedBlurLevel, qMaxCachedBlurLevel, qMaxCachedBlurLevel); + // ensure even dimensions (going to divide by two) + targetRect.setWidth((targetRect.width() + 1) & ~1); + targetRect.setHeight((targetRect.height() + 1) & ~1); - if (src.hasAlphaChannel()) { - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); + QGLBlurTextureInfo *info = 0; + if (blurTextureCache->hasBlurTextureInfo(src)) { + info = blurTextureCache->takeBlurTextureInfo(src); + } else { + m_animatedBlur = false; + m_hint = QGraphicsBlurEffect::QualityHint; + m_singlePass = false; + + QGLFramebufferObjectFormat format; + format.setInternalTextureFormat(GLenum(GL_RGBA)); + QGLFramebufferObject *fbo = qgl_fbo_pool()->acquire(targetRect.size() / 2, format, true); + + if (!fbo) + return false; + + QPainter fboPainter(fbo); + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(fboPainter.paintEngine()); + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + // ensure GL_LINEAR filtering is used for scaling down to half the size + fboPainter.setRenderHint(QPainter::SmoothPixmapTransform); + fboPainter.setCompositionMode(QPainter::CompositionMode_Source); + fboPainter.drawPixmap(qMaxCachedBlurLevel / 2, qMaxCachedBlurLevel / 2, + targetRect.width() / 2 - qMaxCachedBlurLevel, targetRect.height() / 2 - qMaxCachedBlurLevel, src); + + GLuint textures[qNumCachedBlurTextures]; // blur textures + glGenTextures(qNumCachedBlurTextures, textures); + GLuint temp; // temp texture + glGenTextures(1, &temp); + + initializeTexture(temp, fbo->width(), fbo->height()); + m_textureSize = fbo->size(); + + int currentBlur = 0; + + QRect fboRect(0, 0, fbo->width(), fbo->height()); + GLuint sourceTexture = fbo->texture(); + for (int i = 0; i < qNumCachedBlurTextures; ++i) { + int targetBlur = qCachedBlurLevels[i] / 2; + + int blurDelta = qRound(qSqrt(targetBlur * targetBlur - currentBlur * currentBlur)); + QByteArray source = generateGaussianShader(blurDelta); + filter->setSource(source); + + currentBlur = targetBlur; + + // now we're going to be nasty and keep using the same FBO with different textures + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, temp, 0); + + m_horizontalBlur = true; + filter->setOnPainter(&fboPainter); + engine->drawTexture(fboRect, sourceTexture, fbo->size(), fboRect); + filter->removeFromPainter(&fboPainter); + + sourceTexture = textures[i]; + initializeTexture(sourceTexture, fbo->width(), fbo->height()); + + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, textures[i], 0); + + m_horizontalBlur = false; + filter->setOnPainter(&fboPainter); + engine->drawTexture(fboRect, temp, fbo->size(), fboRect); + filter->removeFromPainter(&fboPainter); + } + + glDeleteTextures(1, &temp); + + // reattach the original FBO texture + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, fbo->texture(), 0); + + fboPainter.end(); + + qgl_fbo_pool()->release(fbo); + + info = new QGLBlurTextureInfo(fboRect.size(), textures); + } + + if (!m_haveCached || !m_animatedBlur) { + m_haveCached = true; + m_animatedBlur = true; + m_hint = QGraphicsBlurEffect::AnimationHint; + filter->setSource(qt_gl_interpolate_filter); + } + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + filter->setOnPainter(painter); + + qreal logRadius = qLn(radius()); + + int t; + for (t = -1; t < qNumCachedBlurTextures - 2; ++t) { + if (logRadius < qLogBlurLevel(t+1)) + break; + } + + qreal logBase = t >= 0 ? qLogBlurLevel(t) : 0; + m_t = qBound(qreal(0), (logRadius - logBase) / (qLogBlurLevel(t+1) - logBase), qreal(1)); + + m_textureSize = info->size(); + + glActiveTexture(GL_TEXTURE0 + 3); + if (t >= 0) { + glBindTexture(GL_TEXTURE_2D, info->textureId(t)); + m_targetSize = info->size(); + } else { + QGLTexture *texture = + ctx->d_func()->bindTexture(src, GL_TEXTURE_2D, GL_RGBA, + QGLContext::InternalBindOption + | QGLContext::CanFlipNativePixmapBindOption); + m_targetSize = src.size(); + if (!(texture->options & QGLContext::InvertedYBindOption)) + m_targetSize.setHeight(-m_targetSize.height()); + } + + // restrict the target rect to the max of the radii we are interpolating between + int radiusDelta = qMaxCachedBlurLevel - qCachedBlurLevels[t+1]; + targetRect = targetRect.translated(pos.toPoint()).adjusted(radiusDelta, radiusDelta, -radiusDelta, -radiusDelta); + + radiusDelta /= 2; + QRect sourceRect = QRect(QPoint(), m_textureSize).adjusted(radiusDelta, radiusDelta, -radiusDelta, -radiusDelta); + + engine->drawTexture(targetRect, info->textureId(t+1), m_textureSize, sourceRect); + + glActiveTexture(GL_TEXTURE0 + 3); + glBindTexture(GL_TEXTURE_2D, 0); + + filter->removeFromPainter(painter); + blurTextureCache->insertBlurTextureInfo(src, info); + + return true; } - // ensure GL_LINEAR filtering is used - fboPainter.setRenderHint(QPainter::SmoothPixmapTransform); - filter->setOnPainter(&fboPainter); - fboPainter.drawPixmap(0, 0, src); - filter->removeFromPainter(&fboPainter); - fboPainter.end(); + if (blurTextureCache->hasBlurTextureInfo(src)) + blurTextureCache->clearBlurTextureInfo(src); + + int actualRadius = qRound(radius()); + int filterRadius = actualRadius; + int fastRadii[] = { 1, 2, 3, 5, 8, 15, 25 }; + if (m_hint != QGraphicsBlurEffect::QualityHint) { + uint i = 0; + for (; i < (sizeof(fastRadii)/sizeof(*fastRadii))-1; ++i) { + if (fastRadii[i+1] > filterRadius) + break; + } + filterRadius = fastRadii[i]; + } - QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + m_singlePass = filterRadius <= 3; - // vertical pass, to painter - m_horizontalBlur = false; + if (!m_haveCached || m_animatedBlur || filterRadius != m_cachedRadius) { + // Only regenerate the shader from source if parameters have changed. + m_haveCached = true; + m_animatedBlur = false; + m_cachedRadius = filterRadius; + QByteArray source = generateGaussianShader(filterRadius, m_singlePass); + filter->setSource(source); + } - painter->save(); - // ensure GL_LINEAR filtering is used - painter->setRenderHint(QPainter::SmoothPixmapTransform); - filter->setOnPainter(painter); - engine->drawTexture(src.rect().translated(pos.x(), pos.y()), fbo->texture(), fbo->size(), src.rect().translated(0, fbo->height() - src.height())); - filter->removeFromPainter(painter); - painter->restore(); + QRect targetRect = QRectF(src.rect()).translated(pos).adjusted(-actualRadius, -actualRadius, actualRadius, actualRadius).toAlignedRect(); + + if (m_singlePass) { + // prepare for updateUniforms + m_textureSize = src.size(); + + // ensure GL_LINEAR filtering is used + painter->setRenderHint(QPainter::SmoothPixmapTransform); + filter->setOnPainter(painter); + QBrush pixmapBrush = src; + pixmapBrush.setTransform(QTransform::fromTranslate(pos.x(), pos.y())); + painter->fillRect(targetRect, pixmapBrush); + filter->removeFromPainter(painter); + } else { + QGLFramebufferObjectFormat format; + format.setInternalTextureFormat(GLenum(src.hasAlphaChannel() ? GL_RGBA : GL_RGB)); + QGLFramebufferObject *fbo = qgl_fbo_pool()->acquire(targetRect.size(), format); + + if (!fbo) + return false; + + // prepare for updateUniforms + m_textureSize = src.size(); + + // horizontal pass, to pixmap + m_horizontalBlur = true; + + QPainter fboPainter(fbo); + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); - qgl_fbo_pool()->release(fbo); + // ensure GL_LINEAR filtering is used + fboPainter.setRenderHint(QPainter::SmoothPixmapTransform); + fboPainter.setCompositionMode(QPainter::CompositionMode_Source); + filter->setOnPainter(&fboPainter); + QBrush pixmapBrush = src; + pixmapBrush.setTransform(QTransform::fromTranslate(actualRadius, actualRadius)); + fboPainter.fillRect(QRect(0, 0, targetRect.width(), targetRect.height()), pixmapBrush); + filter->removeFromPainter(&fboPainter); + fboPainter.end(); + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + + // vertical pass, to painter + m_horizontalBlur = false; + m_textureSize = fbo->size(); + + painter->save(); + // ensure GL_LINEAR filtering is used + painter->setRenderHint(QPainter::SmoothPixmapTransform); + filter->setOnPainter(painter); + engine->drawTexture(targetRect, fbo->texture(), fbo->size(), QRect(QPoint(), targetRect.size()).translated(0, fbo->height() - targetRect.height())); + filter->removeFromPainter(painter); + painter->restore(); + + qgl_fbo_pool()->release(fbo); + } return true; } void QGLPixmapBlurFilter::setUniforms(QGLShaderProgram *program) { - if (m_hint == Qt::QualityHint) { - if (m_horizontalBlur) + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (m_animatedBlur) { + program->setUniformValue("interpolateTarget", 3); + program->setUniformValue("interpolationValue", GLfloat(m_t)); + + if (m_textureSize == m_targetSize) { + program->setUniformValue("interpolateMapping", 0.0f, 0.0f, 1.0f, 1.0f); + } else { + float offsetX = (-qMaxCachedBlurLevel - 0.5) / qreal(m_targetSize.width()); + float offsetY = (-qMaxCachedBlurLevel - 0.5) / qreal(m_targetSize.height()); + + if (m_targetSize.height() < 0) + offsetY = 1 + offsetY; + + float scaleX = 2.0f * qreal(m_textureSize.width()) / qreal(m_targetSize.width()); + float scaleY = 2.0f * qreal(m_textureSize.height()) / qreal(m_targetSize.height()); + + program->setUniformValue("interpolateMapping", offsetX, offsetY, scaleX, scaleY); + } + + return; + } + + if (m_hint == QGraphicsBlurEffect::QualityHint) { + if (m_singlePass) + program->setUniformValue("delta", 1.0 / m_textureSize.width(), 1.0 / m_textureSize.height()); + else if (m_horizontalBlur) program->setUniformValue("delta", 1.0 / m_textureSize.width(), 0.0); else program->setUniformValue("delta", 0.0, 1.0 / m_textureSize.height()); } else { - // 1.4 is chosen to most closely match the blurriness of the gaussian blur - // at low radii - qreal blur = radius() / 1.4f; + qreal blur = radius() / qreal(m_cachedRadius); - if (m_horizontalBlur) + if (m_singlePass) + program->setUniformValue("delta", blur / m_textureSize.width(), blur / m_textureSize.height()); + else if (m_horizontalBlur) program->setUniformValue("delta", blur / m_textureSize.width(), 0.0); else program->setUniformValue("delta", 0.0, blur / m_textureSize.height()); @@ -426,12 +786,21 @@ static inline qreal gaussian(qreal dx, qreal sigma) return exp(-dx * dx / (2 * sigma * sigma)) / (Q_2PI * sigma * sigma); } -QByteArray QGLPixmapBlurFilter::generateGaussianShader(int radius, bool dropShadow) +QByteArray QGLPixmapBlurFilter::generateGaussianShader(int radius, bool singlePass, bool dropShadow) { Q_ASSERT(radius >= 1); + radius = qMin(127, radius); + + static QCache<uint, QByteArray> shaderSourceCache; + uint key = radius | (int(singlePass) << 7) | (int(dropShadow) << 8); + QByteArray *cached = shaderSourceCache.object(key); + if (cached) + return *cached; + QByteArray source; source.reserve(1000); + source.append(qt_gl_texture_sampling_helper); source.append("uniform highp vec2 delta;\n"); if (dropShadow) @@ -446,7 +815,7 @@ QByteArray QGLPixmapBlurFilter::generateGaussianShader(int radius, bool dropShad qreal sigma = radius / 1.65; qreal sum = 0; - for (int i = -radius; i <= radius; ++i) { + for (int i = -radius; i < radius; ++i) { float value = gaussian(i, sigma); gaussianComponents << value; sum += value; @@ -464,43 +833,67 @@ QByteArray QGLPixmapBlurFilter::generateGaussianShader(int radius, bool dropShad weights << weight; } - // odd size ? - if (gaussianComponents.size() & 1) { - sampleOffsets << radius; - weights << gaussianComponents.last(); - } - - int currentVariable = 1; - source.append(" mediump vec4 sample = vec4(0.0);\n"); - source.append(" mediump vec2 coord;\n"); - - qreal weightSum = 0; - source.append(" mediump float c;\n"); - for (int i = 0; i < sampleOffsets.size(); ++i) { - qreal delta = sampleOffsets.at(i); - - ++currentVariable; + int limit = sampleOffsets.size(); + if (singlePass) + limit *= limit; + + QByteArray baseCoordinate = "srcCoords"; + + for (int i = 0; i < limit; ++i) { + QByteArray coordinate = baseCoordinate; + + qreal weight; + if (singlePass) { + const int xIndex = i % sampleOffsets.size(); + const int yIndex = i / sampleOffsets.size(); + + const qreal deltaX = sampleOffsets.at(xIndex); + const qreal deltaY = sampleOffsets.at(yIndex); + weight = weights.at(xIndex) * weights.at(yIndex); + + if (!qFuzzyCompare(deltaX, deltaY)) { + coordinate.append(" + vec2(delta.x * float("); + coordinate.append(QByteArray::number(deltaX)); + coordinate.append("), delta.y * float("); + coordinate.append(QByteArray::number(deltaY)); + coordinate.append("))"); + } else if (!qFuzzyIsNull(deltaX)) { + coordinate.append(" + delta * float("); + coordinate.append(QByteArray::number(deltaX)); + coordinate.append(")"); + } + } else { + const qreal delta = sampleOffsets.at(i); + weight = weights.at(i); + if (!qFuzzyIsNull(delta)) { + coordinate.append(" + delta * float("); + coordinate.append(QByteArray::number(delta)); + coordinate.append(")"); + } + } - QByteArray coordinate = "srcCoords"; - if (delta != qreal(0)) { - coordinate.append(" + delta * float("); - coordinate.append(QByteArray::number(delta)); - coordinate.append(")"); + if (i == 0) { + if (dropShadow) + source.append(" mediump float sample = "); + else + source.append(" mediump vec4 sample = "); + } else { + if (dropShadow) + source.append(" sample += "); + else + source.append(" sample += "); } - source.append(" coord = "); + source.append("texture2D(src, "); source.append(coordinate); - source.append(";\n"); + source.append(")"); if (dropShadow) - source.append(" sample += texture2D(src, coord).a * shadowColor"); - else - source.append(" sample += texture2D(src, coord)"); + source.append(".a"); - weightSum += weights.at(i); - if (weights.at(i) != qreal(1)) { + if (!qFuzzyCompare(weight, qreal(1))) { source.append(" * float("); - source.append(QByteArray::number(weights.at(i))); + source.append(QByteArray::number(weight)); source.append(");\n"); } else { source.append(";\n"); @@ -508,86 +901,100 @@ QByteArray QGLPixmapBlurFilter::generateGaussianShader(int radius, bool dropShad } source.append(" return "); + if (dropShadow) + source.append("shadowColor * "); source.append("sample;\n"); source.append("}\n"); + cached = new QByteArray(source); + shaderSourceCache.insert(key, cached); + return source; } -QGLPixmapDropShadowFilter::QGLPixmapDropShadowFilter(Qt::RenderHint hint) +QGLPixmapDropShadowFilter::QGLPixmapDropShadowFilter(QGraphicsBlurEffect::BlurHint hint) : m_haveCached(false) - , m_cachedRadius(5) + , m_cachedRadius(0) , m_hint(hint) { - if (hint == Qt::PerformanceHint) { - QGLPixmapDropShadowFilter *filter = const_cast<QGLPixmapDropShadowFilter *>(this); - filter->setSource(qt_gl_drop_shadow_filter_fast); - m_haveCached = true; - } } bool QGLPixmapDropShadowFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const { QGLPixmapDropShadowFilter *filter = const_cast<QGLPixmapDropShadowFilter *>(this); - int radius = this->blurRadius(); - if (!m_haveCached || (m_hint == Qt::QualityHint && radius != m_cachedRadius)) { + int actualRadius = qRound(blurRadius()); + int filterRadius = actualRadius; + m_singlePass = filterRadius <= 3; + + if (!m_haveCached || filterRadius != m_cachedRadius) { // Only regenerate the shader from source if parameters have changed. m_haveCached = true; - m_cachedRadius = radius; - filter->setSource(QGLPixmapBlurFilter::generateGaussianShader(radius, true)); + m_cachedRadius = filterRadius; + QByteArray source = QGLPixmapBlurFilter::generateGaussianShader(filterRadius, m_singlePass, true); + filter->setSource(source); } - QGLFramebufferObjectFormat format; - format.setInternalTextureFormat(GLenum(src.hasAlphaChannel() ? GL_RGBA : GL_RGB)); - QGLFramebufferObject *fbo = qgl_fbo_pool()->acquire(src.size(), format); - - if (!fbo) - return false; - - glBindTexture(GL_TEXTURE_2D, fbo->texture()); + QRect targetRect = QRectF(src.rect()).translated(pos + offset()).adjusted(-actualRadius, -actualRadius, actualRadius, actualRadius).toAlignedRect(); + + if (m_singlePass) { + // prepare for updateUniforms + m_textureSize = src.size(); + + painter->save(); + // ensure GL_LINEAR filtering is used + painter->setRenderHint(QPainter::SmoothPixmapTransform); + filter->setOnPainter(painter); + QBrush pixmapBrush = src; + pixmapBrush.setTransform(QTransform::fromTranslate(pos.x() + offset().x(), pos.y() + offset().y())); + painter->fillRect(targetRect, pixmapBrush); + filter->removeFromPainter(painter); + painter->restore(); + } else { + QGLFramebufferObjectFormat format; + format.setInternalTextureFormat(GLenum(src.hasAlphaChannel() ? GL_RGBA : GL_RGB)); + QGLFramebufferObject *fbo = qgl_fbo_pool()->acquire(targetRect.size(), format); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + if (!fbo) + return false; - // prepare for updateUniforms - m_textureSize = src.size(); + // prepare for updateUniforms + m_textureSize = src.size(); - // horizontal pass, to pixmap - m_horizontalBlur = true; + // horizontal pass, to pixmap + m_horizontalBlur = true; - QPainter fboPainter(fbo); + QPainter fboPainter(fbo); - if (src.hasAlphaChannel()) { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); - } - // ensure GL_LINEAR filtering is used - fboPainter.setRenderHint(QPainter::SmoothPixmapTransform); - filter->setOnPainter(&fboPainter); - fboPainter.drawPixmap(0, 0, src); - filter->removeFromPainter(&fboPainter); - fboPainter.end(); - - QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); - - // vertical pass, to painter - m_horizontalBlur = false; - - painter->save(); - // ensure GL_LINEAR filtering is used - painter->setRenderHint(QPainter::SmoothPixmapTransform); - filter->setOnPainter(painter); - QPointF ofs = offset(); - engine->drawTexture(src.rect().translated(pos.x() + ofs.x(), pos.y() + ofs.y()), fbo->texture(), fbo->size(), src.rect().translated(0, fbo->height() - src.height())); - filter->removeFromPainter(painter); - painter->restore(); - - qgl_fbo_pool()->release(fbo); + // ensure GL_LINEAR filtering is used + fboPainter.setRenderHint(QPainter::SmoothPixmapTransform); + fboPainter.setCompositionMode(QPainter::CompositionMode_Source); + filter->setOnPainter(&fboPainter); + QBrush pixmapBrush = src; + pixmapBrush.setTransform(QTransform::fromTranslate(actualRadius, actualRadius)); + fboPainter.fillRect(QRect(0, 0, targetRect.width(), targetRect.height()), pixmapBrush); + filter->removeFromPainter(&fboPainter); + fboPainter.end(); + + QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); + + // vertical pass, to painter + m_horizontalBlur = false; + m_textureSize = fbo->size(); + + painter->save(); + // ensure GL_LINEAR filtering is used + painter->setRenderHint(QPainter::SmoothPixmapTransform); + filter->setOnPainter(painter); + engine->drawTexture(targetRect, fbo->texture(), fbo->size(), src.rect().translated(0, fbo->height() - src.height())); + filter->removeFromPainter(painter); + painter->restore(); + + qgl_fbo_pool()->release(fbo); + } // Now draw the actual pixmap over the top. painter->drawPixmap(pos, src, srcRect); @@ -597,8 +1004,11 @@ bool QGLPixmapDropShadowFilter::processGL(QPainter *painter, const QPointF &pos, void QGLPixmapDropShadowFilter::setUniforms(QGLShaderProgram *program) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + QColor col = color(); - if (m_horizontalBlur) { + if (m_horizontalBlur && !m_singlePass) { program->setUniformValue("shadowColor", 1.0f, 1.0f, 1.0f, 1.0f); } else { qreal alpha = col.alphaF(); @@ -607,17 +1017,20 @@ void QGLPixmapDropShadowFilter::setUniforms(QGLShaderProgram *program) col.blueF() * alpha, alpha); } - if (m_hint == Qt::QualityHint) { - if (m_horizontalBlur) + + if (m_hint == QGraphicsBlurEffect::QualityHint) { + if (m_singlePass) + program->setUniformValue("delta", 1.0 / m_textureSize.width(), 1.0 / m_textureSize.height()); + else if (m_horizontalBlur) program->setUniformValue("delta", 1.0 / m_textureSize.width(), 0.0); else program->setUniformValue("delta", 0.0, 1.0 / m_textureSize.height()); } else { - // 1.4 is chosen to most closely match the blurriness of the gaussian blur - // at low radii - qreal blur = blurRadius() / 1.4f; + qreal blur = blurRadius() / qreal(m_cachedRadius); - if (m_horizontalBlur) + if (m_singlePass) + program->setUniformValue("delta", blur / m_textureSize.width(), blur / m_textureSize.height()); + else if (m_horizontalBlur) program->setUniformValue("delta", blur / m_textureSize.width(), 0.0); else program->setUniformValue("delta", 0.0, blur / m_textureSize.height()); |