diff --git a/.arcconfig b/.arcconfig --- a/.arcconfig +++ b/.arcconfig @@ -1,4 +1,4 @@ { - "phabricator.uri" : "https://phabricator.kde.org/" + "phabricator.uri" : "https://phabricator.kde.org/source/kwin/" } diff --git a/effects/blur/blur.h b/effects/blur/blur.h --- a/effects/blur/blur.h +++ b/effects/blur/blur.h @@ -26,6 +26,7 @@ #include #include +#include namespace KWayland { @@ -43,26 +44,26 @@ class BlurEffect : public KWin::Effect { Q_OBJECT - Q_PROPERTY(int blurRadius READ blurRadius) Q_PROPERTY(bool cacheTexture READ isCacheTexture) public: BlurEffect(); ~BlurEffect(); static bool supported(); static bool enabledByDefault(); + void configureBlurStrength(); void reconfigure(ReconfigureFlags flags); void prePaintScreen(ScreenPrePaintData &data, int time); void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); void drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); void paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity); // for dynamic setting extraction - int blurRadius() const; bool isCacheTexture() const { return m_shouldCache; } + virtual bool provides(Feature feature); int requestedEffectChainPosition() const override { @@ -80,27 +81,36 @@ QRect expand(const QRect &rect) const; QRegion expand(const QRegion ®ion) const; QRegion blurRegion(const EffectWindow *w) const; - bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; + bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data); void updateBlurRegion(EffectWindow *w) const; void doSimpleBlur(EffectWindow *w, const float opacity, const QMatrix4x4 &screenProjection); void doBlur(const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection); void doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection); - void uploadRegion(QVector2D *&map, const QRegion ®ion); - void uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical); + void uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations); + void uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &renderRegion, const QRegion &windowRegion); + bool renderTargetsValid(); private: - BlurShader *shader; GLShader *m_simpleShader; - GLRenderTarget *target = nullptr; - GLTexture tex; + GLRenderTarget *m_simpleTarget; + + BlurShader *shader; + QVector renderTargets; + QVector renderTextures; long net_wm_blur_region; QRegion m_damagedArea; // keeps track of the area which has been damaged (from bottom to top) QRegion m_paintedArea; // actually painted area which is greater than m_damagedArea QRegion m_currentBlur; // keeps track of the currently blured area of non-caching windows(from bottom to top) bool m_shouldCache; + bool m_useSimpleBlur; + + int m_downSampleIterations; // number of times the texture will be downsized to half size + int m_offset; + int m_expandSize; struct BlurWindowInfo { - GLTexture blurredBackground; // keeps the horizontally blurred background + GLTexture blurredBackground; + QSize windowSize; QRegion damagedRegion; QPoint windowPos; bool dropCache; @@ -118,6 +128,7 @@ if (feature == Blur) { return true; } + return KWin::Effect::provides(feature); } diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -32,6 +32,8 @@ #include #include +#define BORDER_SIZE 5 + namespace KWin { @@ -42,16 +44,17 @@ initConfig(); shader = BlurShader::create(); m_simpleShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("logout-blur.frag")); + m_simpleTarget = new GLRenderTarget(GLTexture(GL_RGBA8, 1, 1)); + if (!m_simpleShader->isValid()) { qCDebug(KWINEFFECTS) << "Simple blur shader failed to load"; } - updateTexture(); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. - if (shader && shader->isValid() && target->valid()) { + if (shader && shader->isValid() && renderTargetsValid()) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); KWayland::Server::Display *display = effects->waylandDisplay(); if (display) { @@ -68,7 +71,7 @@ connect(effects, SIGNAL(screenGeometryChanged(QSize)), this, SLOT(slotScreenGeometryChanged())); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { - if (shader && shader->isValid() && target->valid()) { + if (shader && shader->isValid() && renderTargetsValid()) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); } } @@ -84,8 +87,20 @@ windows.clear(); delete m_simpleShader; + m_simpleShader = NULL; + delete shader; - delete target; + shader = NULL; + + for (int i = 0; i < renderTargets.size(); i++) { + delete renderTargets[i]; + renderTargets[i] = NULL; + + renderTextures[i].discard(); + } + + renderTargets.clear(); + renderTextures.clear(); } void BlurEffect::slotScreenGeometryChanged() @@ -98,28 +113,186 @@ effects->doneOpenGLContextCurrent(); } -void BlurEffect::updateTexture() { - delete target; - // Offscreen texture that's used as the target for the horizontal blur pass - // and the source for the vertical pass. - tex = GLTexture(GL_RGBA8, effects->virtualScreenSize()); - tex.setFilter(GL_LINEAR); - tex.setWrapMode(GL_CLAMP_TO_EDGE); +bool BlurEffect::renderTargetsValid() +{ + if (renderTargets.isEmpty()) { + return false; + } + + for (int i = 0; i < renderTargets.size(); i++) { + if (!renderTargets[i]->valid()) { + return false; + } + } + + return true; +} + +void BlurEffect::updateTexture() +{ + for (int i = 0; i < renderTargets.size(); i++) { + delete renderTargets[i]; + renderTargets[i] = NULL; + + renderTextures[i].discard(); + } + + renderTargets.clear(); + renderTextures.clear(); + + for (int i = 0; i <= m_downSampleIterations; i++) { + renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize() / qPow(2, i))); + renderTextures.last().setFilter(GL_LINEAR); + renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + renderTargets.append(new GLRenderTarget(renderTextures.last())); + } + + // This last set is used as a temporary helper texture + renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize())); + renderTextures.last().setFilter(GL_LINEAR); + renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + renderTargets.append(new GLRenderTarget(renderTextures.last())); +} - target = new GLRenderTarget(tex); +void BlurEffect::configureBlurStrength() +{ + /** + * Explanation for the magic numbers: + * + * The texture blur amount is depends on the downsampling iterations and the offset value. + * By changing the offset we can alter the blur amount without relying on further downsampling. + * But there is a maximum value of offset per downsample iteration before we get artifacts. + * For example the maximum value of offset before there is almost clearly visible artifacts: + * - for 1 iteration is 2 + * - for 2 iterations is 4 + * - for 3 iterations is 8 + * + * For good amount of blur (at current common resolutions) we need at least 4 iterations. + * Offset value of 1 on 4 iterations is still too much blur to consider it using it as minimum. + * This means we can't have constant iteration value. + * If we left the offset value constant and only changed the iteration number then we would + * have good minimum and maximum blur amount, but there would be around only 5 blur strength + * steps. + * This means we can't have constant offset value. + * As a compromise we have to use hardcoded magic numbers. This works for common resolutions. + * In the future as we enter the age of high resolution monitors (8K and above) we have to + * either increase the iterations or add more blur strength steps with increased iterations. + * For now it would be pointless to add more blur strength, as the currently defined maximum + * is (in my opinion) more than enough. + **/ + + switch(BlurConfig::blurStrength()) { + case 1: + m_downSampleIterations = 1; + m_offset = 1; + m_expandSize = 5; + break; + + case 2: + m_downSampleIterations = 2; + m_offset = 1; + m_expandSize = 10; + break; + + case 3: + m_downSampleIterations = 2; + m_offset = 2; + m_expandSize = 15; + break; + + case 4: + m_downSampleIterations = 2; + m_offset = 3; + m_expandSize = 25; + break; + + case 5: + m_downSampleIterations = 2; + m_offset = 4; + m_expandSize = 30; + break; + + case 6: + m_downSampleIterations = 3; + m_offset = 3; + m_expandSize = 40; + break; + + case 7: + m_downSampleIterations = 3; + m_offset = 4; + m_expandSize = 50; + break; + + case 8: + m_downSampleIterations = 3; + m_offset = 5; + m_expandSize = 60; + break; + + case 9: + m_downSampleIterations = 3; + m_offset = 6; + m_expandSize = 80; + break; + + case 10: + m_downSampleIterations = 3; + m_offset = 7; + m_expandSize = 100; + break; + + case 11: + m_downSampleIterations = 3; + m_offset = 8; + m_expandSize = 120; + break; + + case 12: + m_downSampleIterations = 4; + m_offset = 5; + m_expandSize = 140; + break; + + case 13: + m_downSampleIterations = 4; + m_offset = 6; + m_expandSize = 150; + break; + + case 14: + m_downSampleIterations = 4; + m_offset = 7; + m_expandSize = 170; + break; + + case 15: + m_downSampleIterations = 4; + m_offset = 8; + m_expandSize = 200; + break; + + default: + m_downSampleIterations = 1; + m_offset = 1; + m_expandSize = 2; + } } void BlurEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) - + BlurConfig::self()->read(); - int radius = qBound(2, BlurConfig::blurRadius(), 14); - if (shader) - shader->setRadius(radius); - + m_shouldCache = BlurConfig::cacheTexture(); - + m_useSimpleBlur = BlurConfig::useSimpleBlur(); + + configureBlurStrength(); + updateTexture(); + windows.clear(); if (!shader || !shader->isValid()) { @@ -136,13 +309,16 @@ if (net_wm_blur_region != XCB_ATOM_NONE) { value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); + if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { const uint32_t *cardinals = reinterpret_cast(value.constData()); + for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; + region += QRect(x, y, w, h); } } @@ -171,12 +347,12 @@ if (surf) { windows[w].blurChangedConnection = connect(surf, &KWayland::Server::SurfaceInterface::blurChanged, this, [this, w] () { - if (w) { updateBlurRegion(w); } }); } + updateBlurRegion(w); } @@ -193,6 +369,7 @@ if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) { updateBlurRegion(w); CacheEntry it = windows.find(w); + if (it != windows.end()) { const QRect screen = effects->virtualScreenGeometry(); it->damagedRegion = expand(blurRegion(w).translated(w->pos())) & screen; @@ -225,13 +402,13 @@ if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) supported = false; } + return supported; } QRect BlurEffect::expand(const QRect &rect) const { - const int radius = shader->radius(); - return rect.adjusted(-radius, -radius, radius, radius); + return rect.adjusted(-m_expandSize, -m_expandSize, m_expandSize, m_expandSize); } QRegion BlurEffect::expand(const QRegion ®ion) const @@ -250,13 +427,16 @@ QRegion region; const QVariant value = w->data(WindowBlurBehindRole); + if (value.isValid()) { const QRegion appRegion = qvariant_cast(value); + if (!appRegion.isEmpty()) { if (w->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) { region = w->shape(); region -= w->decorationInnerRect(); } + region |= appRegion.translated(w->contentsRect().topLeft()) & w->decorationInnerRect(); } else { @@ -274,35 +454,43 @@ return region; } -void BlurEffect::uploadRegion(QVector2D *&map, const QRegion ®ion) +void BlurEffect::uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations) { - for (const QRect &r : region) { - const QVector2D topLeft(r.x(), r.y()); - const QVector2D topRight(r.x() + r.width(), r.y()); - const QVector2D bottomLeft(r.x(), r.y() + r.height()); - const QVector2D bottomRight(r.x() + r.width(), r.y() + r.height()); - - // First triangle - *(map++) = topRight; - *(map++) = topLeft; - *(map++) = bottomLeft; - - // Second triangle - *(map++) = bottomLeft; - *(map++) = bottomRight; - *(map++) = topRight; + for (int i = 0; i <= downSampleIterations; i++) { + const int divisionRatio = qPow(2, i); + + for (const QRect &r : region) { + const QVector2D topLeft( r.x() / divisionRatio, r.y() / divisionRatio); + const QVector2D topRight( (r.x() + r.width()) / divisionRatio, r.y() / divisionRatio); + const QVector2D bottomLeft( r.x() / divisionRatio, (r.y() + r.height()) / divisionRatio); + const QVector2D bottomRight((r.x() + r.width()) / divisionRatio, (r.y() + r.height()) / divisionRatio); + + // First triangle + *(map++) = topRight; + *(map++) = topLeft; + *(map++) = bottomLeft; + + // Second triangle + *(map++) = bottomLeft; + *(map++) = bottomRight; + *(map++) = topRight; + } } } -void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical) +void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &renderRegion, const QRegion &windowRegion) { - const int vertexCount = (horizontal.rectCount() + vertical.rectCount()) * 6; + const int vertexCount = ((blurRegion.rectCount() * (m_downSampleIterations + 1)) + renderRegion.rectCount() + windowRegion.rectCount()) * 6; + if (!vertexCount) return; QVector2D *map = (QVector2D *) vbo->map(vertexCount * sizeof(QVector2D)); - uploadRegion(map, horizontal); - uploadRegion(map, vertical); + + uploadRegion(map, blurRegion, m_downSampleIterations); + uploadRegion(map, renderRegion, 0); + uploadRegion(map, windowRegion, 0); + vbo->unmap(); const GLVertexAttrib layout[] = { @@ -338,10 +526,11 @@ // to blur an area partially we have to shrink the opaque area of a window QRegion newClip; const QRegion oldClip = data.clip; - const int radius = shader->radius(); + for (const QRect &rect : data.clip) { - newClip |= rect.adjusted(radius,radius,-radius,-radius); + newClip |= rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize); } + data.clip = newClip; const QRegion oldPaint = data.paint; @@ -360,15 +549,15 @@ const QRegion expandedBlur = expand(blurArea) & screen; if (m_shouldCache && !w->isDeleted()) { - // we are caching the horizontally blurred background texture + // we are caching the blurred background texture // if a window underneath the blurred area is damaged we have to // update the cached texture QRegion damagedCache; CacheEntry it = windows.find(w); if (it != windows.end() && !it->dropCache && it->windowPos == w->pos() && - it->blurredBackground.size() == expandedBlur.boundingRect().size()) { + it->windowSize == expandedBlur.boundingRect().size()) { damagedCache = (expand(expandedBlur & m_damagedArea) | (it->damagedRegion & data.paint)) & expandedBlur; } else { @@ -380,6 +569,7 @@ // In order to be able to recalculate this area we have to make sure the // background area is painted before. data.paint |= expand(damagedArea); + if (it != windows.end()) { // In case we already have a texture cache mark the dirty regions invalid. it->damagedRegion &= expandedBlur; @@ -406,6 +596,7 @@ data.paint |= expandedBlur; // we keep track of the "damage propagation" m_damagedArea |= expand(expandedBlur & m_damagedArea) & blurArea; + // we have to check again whether we do not damage a blurred area // of a window we do not cache if (expandedBlur.intersects(m_currentBlur)) { @@ -426,9 +617,9 @@ m_paintedArea |= data.paint; } -bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const +bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) { - if (!target->valid() || !shader || !shader->isValid()) + if (!renderTargetsValid() || !shader || !shader->isValid()) return false; if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) @@ -455,23 +646,27 @@ void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { const QRect screen = GLRenderTarget::virtualScreenGeometry(); + if (shouldBlur(w, mask, data)) { QRegion shape = region & blurRegion(w).translated(w->pos()) & screen; // let's do the evil parts - someone wants to blur behind a transformed window const bool translated = data.xTranslation() || data.yTranslation(); const bool scaled = data.xScale() != 1 || data.yScale() != 1; + if (scaled) { QPoint pt = shape.boundingRect().topLeft(); QVector shapeRects = shape.rects(); shape = QRegion(); // clear + foreach (QRect r, shapeRects) { r.moveTo(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); shape |= r; } + shape = shape & region; //Only translated, not scaled @@ -483,7 +678,11 @@ if (!shape.isEmpty()) { if (w->isFullScreen() && GLRenderTarget::blitSupported() && m_simpleShader->isValid() && !GLPlatform::instance()->supports(LimitedNPOT) && shape.boundingRect() == w->geometry()) { - doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); + if (m_useSimpleBlur) { + doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); + } else { + doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix()); + } } else if (m_shouldCache && !translated && !w->isDeleted()) { doCachedBlur(w, region, data.opacity(), data.screenProjectionMatrix()); } else { @@ -499,11 +698,13 @@ void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) { const QRect screen = effects->virtualScreenGeometry(); - bool valid = target->valid() && shader && shader->isValid(); - QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen; + bool valid = renderTargetsValid() && shader && shader->isValid(); + QRegion shape = frame->geometry().adjusted(-BORDER_SIZE, -BORDER_SIZE, BORDER_SIZE, BORDER_SIZE) & screen; + if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix()); } + effects->paintEffectFrame(frame, region, opacity, frameOpacity); } @@ -514,81 +715,109 @@ blurTexture.setFilter(GL_LINEAR_MIPMAP_LINEAR); blurTexture.setWrapMode(GL_CLAMP_TO_EDGE); - target->attachTexture(blurTexture); - target->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); + m_simpleTarget->attachTexture(blurTexture); + m_simpleTarget->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); // Unmodified base image ShaderBinder binder(m_simpleShader); QMatrix4x4 mvp = screenProjection; mvp.translate(w->x(), w->y()); + m_simpleShader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_simpleShader->setUniform("u_alphaProgress", opacity); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blurTexture.bind(); blurTexture.generateMipmaps(); blurTexture.render(infiniteRegion(), w->geometry()); blurTexture.unbind(); + glDisable(GL_BLEND); } void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection) { - const QRegion expanded = expand(shape) & screen; - const QRect r = expanded.boundingRect(); - - // Upload geometry for the horizontal and vertical passes + QRegion expandedBlurRegion = expand(shape) & expand(screen); + QRect expandedBlurRect = expandedBlurRegion.boundingRect() & screen; + + // Upload geometry for the down and upsample iterations GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, expanded, shape); + uploadGeometry(vbo, expandedBlurRegion, QRegion(), shape); vbo->bindArrays(); - - const qreal scale = GLRenderTarget::virtualScreenScale(); - - // Create a scratch texture and copy the area in the back buffer that we're - // going to blur into it - // for HIGH DPI scratch is captured in native resolution, it is then implicitly downsampled - // when rendering into tex - GLTexture scratch(GL_RGBA8, r.width() * scale, r.height() * scale); - scratch.setFilter(GL_LINEAR); - scratch.setWrapMode(GL_CLAMP_TO_EDGE); - scratch.bind(); - - const QRect sg = GLRenderTarget::virtualScreenGeometry(); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (r.x() - sg.x()) * scale, (sg.height() - sg.y() - r.y() - r.height()) * scale, - scratch.width(), scratch.height()); - - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(tex); - GLRenderTarget::pushRenderTarget(target); - - shader->bind(); - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / r.width()); - - QMatrix4x4 modelViewProjectionMatrix; - modelViewProjectionMatrix.ortho(0, tex.width(), tex.height(), 0 , 0, 65535); - shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); - - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - QMatrix4x4 textureMatrix; - textureMatrix.scale(1.0 / r.width(), -1.0 / r.height(), 1); - textureMatrix.translate(-r.x(), (-r.height() - r.y()), 0); - shader->setTextureMatrix(textureMatrix); - - vbo->draw(GL_TRIANGLES, 0, expanded.rectCount() * 6); - + + int blurRectCount = expandedBlurRegion.rectCount() * 6; + + // Copy part of the screen into this texture + renderTextures.last().bind(); + glCopyTexSubImage2D( + GL_TEXTURE_2D, + 0, + expandedBlurRect.x(), + effects->virtualScreenSize().height() - expandedBlurRect.y() - expandedBlurRect.height(), + expandedBlurRect.x(), + effects->virtualScreenSize().height() - expandedBlurRect.y() - expandedBlurRect.height(), + expandedBlurRect.width(), + expandedBlurRect.height() + ); + + shader->bind(BlurShader::copySampleType); + + shader->setModelViewProjectionMatrix(screenProjection); + shader->setTextureSize(screen.size()); + shader->setBlurRect(shape.boundingRect().adjusted(BORDER_SIZE, BORDER_SIZE, -BORDER_SIZE, -BORDER_SIZE), screen.size()); + + GLRenderTarget::pushRenderTarget(renderTargets[0]); + vbo->draw(GL_TRIANGLES, 0, blurRectCount); GLRenderTarget::popRenderTarget(); - scratch.unbind(); - scratch.discard(); - - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - tex.bind(); - - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / tex.height()); + + shader->unbind(); + QMatrix4x4 modelViewProjectionMatrix; + + //Downsample + shader->bind(BlurShader::downSampleType); + shader->setOffset(m_offset); + + for (int i = 1; i <= m_downSampleIterations; i++) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, renderTextures[i].width(), renderTextures[i].height(), 0 , 0, 65535); + + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + shader->setTextureSize(renderTextures[i].size()); + + //Copy the image from this texture + renderTextures[i - 1].bind(); + + //Render to the texture attached to this FrameBufferObject + GLRenderTarget::pushRenderTarget(renderTargets[i]); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } + + shader->unbind(); + + //Upsample + shader->bind(BlurShader::upSampleType); + shader->setOffset(m_offset); + + for (int i = m_downSampleIterations - 1; i > 0; i--) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, renderTextures[i].width(), renderTextures[i].height(), 0 , 0, 65535); + + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + shader->setTextureSize(renderTextures[i].size()); + + //Copy the image from this texture + renderTextures[i + 1].bind(); + + //Render to the texture attached to this FrameBufferObject + GLRenderTarget::pushRenderTarget(renderTargets[i]); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } + // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { glEnable(GL_BLEND); @@ -603,58 +832,50 @@ glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(0, -tex.height(), 0); - shader->setTextureMatrix(textureMatrix); + //Final upscale to the screen shader->setModelViewProjectionMatrix(screenProjection); + shader->setTextureSize(renderTextures[0].size()); + + //Copy the image from this texture + renderTextures[1].bind(); - vbo->draw(GL_TRIANGLES, expanded.rectCount() * 6, shape.rectCount() * 6); - vbo->unbindArrays(); + //Render to the screen + vbo->draw(GL_TRIANGLES, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); if (opacity < 1.0) { glDisable(GL_BLEND); } - tex.unbind(); + vbo->unbindArrays(); shader->unbind(); } void BlurEffect::doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection) { - const QRect screen = effects->virtualScreenGeometry(); - const QRegion blurredRegion = blurRegion(w).translated(w->pos()) & screen; - const QRegion expanded = expand(blurredRegion) & screen; - const QRect r = expanded.boundingRect(); - - // The background texture we get is only partially valid. - + const QRect screenRect = effects->virtualScreenGeometry(); + const QRegion blurredRegion = blurRegion(w).translated(w->pos()) & screenRect; + + QRegion expandedBlurredRegion = expand(blurredRegion) & expand(screenRect); + QRect expandedBlurredRect = expandedBlurredRegion.boundingRect() & screenRect; + CacheEntry it = windows.find(w); if (it == windows.end()) { BlurWindowInfo bwi; - bwi.blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); - bwi.damagedRegion = expanded; + bwi.blurredBackground = GLTexture(GL_RGBA8, screenRect.width(),screenRect.height()); + bwi.windowSize = expandedBlurredRect.size(); + bwi.damagedRegion = expandedBlurredRegion; bwi.dropCache = false; bwi.windowPos = w->pos(); it = windows.insert(w, bwi); - } else if (it->blurredBackground.size() != r.size()) { - it->blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); + } else if (it->windowSize != expandedBlurredRect.size()) { + it->windowSize = expandedBlurredRect.size(); it->dropCache = false; it->windowPos = w->pos(); } else if (it->windowPos != w->pos()) { it->dropCache = false; it->windowPos = w->pos(); } - - GLTexture targetTexture = it->blurredBackground; - targetTexture.setFilter(GL_LINEAR); - targetTexture.setWrapMode(GL_CLAMP_TO_EDGE); - shader->bind(); - QMatrix4x4 textureMatrix; - QMatrix4x4 modelViewProjectionMatrix; - + /** * Which part of the background texture can be updated ? * @@ -686,99 +907,137 @@ * can do so (e.g. SlidingPopups). Hence we have to make the compromise that we update * "damagedRegion & region" of the cache but only mark "validUpdate" as valid. **/ - const QRegion damagedRegion = it->damagedRegion; - const QRegion updateBackground = damagedRegion & region; - const QRegion validUpdate = damagedRegion - expand(damagedRegion - region); - - const QRegion horizontal = validUpdate.isEmpty() ? QRegion() : (updateBackground & screen); - const QRegion vertical = blurredRegion & region; - - const int horizontalOffset = 0; - const int horizontalCount = horizontal.rectCount() * 6; - - const int verticalOffset = horizontalCount; - const int verticalCount = vertical.rectCount() * 6; - + + const QRegion validUpdateRegion = it->damagedRegion - expand(it->damagedRegion - region); + + QRegion windowRegion = blurredRegion & region; + QRegion renderRegion = it->damagedRegion & region & windowRegion; + QRegion blurRegion = expand(renderRegion); + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, horizontal, vertical); - + uploadGeometry(vbo, blurRegion, renderRegion, windowRegion); vbo->bindArrays(); - - if (!validUpdate.isEmpty()) { - const QRect updateRect = (expand(updateBackground) & expanded).boundingRect(); - // First we have to copy the background from the frontbuffer - // into a scratch texture (in this case "tex"). - tex.bind(); - - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, updateRect.x(), effects->virtualScreenSize().height() - updateRect.y() - updateRect.height(), - updateRect.width(), updateRect.height()); - - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(targetTexture); - GLRenderTarget::pushRenderTarget(target); - - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / tex.width()); - - modelViewProjectionMatrix.ortho(0, r.width(), r.height(), 0 , 0, 65535); - modelViewProjectionMatrix.translate(-r.x(), -r.y(), 0); + + int blurRectCount = blurRegion.rectCount() * 6; + + if (!validUpdateRegion.isEmpty()) { + QMatrix4x4 modelViewProjectionMatrix; + QRect blurRect = blurRegion.boundingRect() & screenRect; + + // Copy part of the screen into this texture + renderTextures.last().bind(); + glCopyTexSubImage2D( + GL_TEXTURE_2D, + 0, + blurRect.x(), + effects->virtualScreenSize().height() - blurRect.y() - blurRect.height(), + blurRect.x(), + effects->virtualScreenSize().height() - blurRect.y() - blurRect.height(), + blurRect.width(), + blurRect.height() + ); + + shader->bind(BlurShader::copySampleType); + + shader->setModelViewProjectionMatrix(screenProjection); + shader->setTextureSize(screenRect.size()); + shader->setBlurRect(blurredRegion.boundingRect().adjusted(BORDER_SIZE, BORDER_SIZE, -BORDER_SIZE, -BORDER_SIZE), screenRect.size()); + + GLRenderTarget::pushRenderTarget(renderTargets[0]); + vbo->draw(GL_TRIANGLES, 0, blurRectCount); + GLRenderTarget::popRenderTarget(); + + shader->unbind(); + + // Downsample + shader->bind(BlurShader::downSampleType); + shader->setOffset(m_offset); + + for (int i = 1; i <= m_downSampleIterations; i++) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, renderTextures[i].width(), renderTextures[i].height(), 0 , 0, 65535); + + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + shader->setTextureSize(renderTextures[i].size()); + + // Copy the image from this texture + renderTextures[i - 1].bind(); + + // Render to the texture attachment of this FrameBufferObject + GLRenderTarget::pushRenderTarget(renderTargets[i]); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } + + shader->unbind(); + + // Upsample + shader->bind(BlurShader::upSampleType); + shader->setOffset(m_offset); + + for (int i = m_downSampleIterations - 1; i > 0; i--) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, renderTextures[i].width(), renderTextures[i].height(), 0 , 0, 65535); + + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + shader->setTextureSize(renderTextures[i].size()); + + // Copy the image from this texture + renderTextures[i + 1].bind(); + + // Render to the texture attachment of this FrameBufferObject + GLRenderTarget::pushRenderTarget(renderTargets[i]); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } + + //Final upsample to the texture that is used for rendering to the screen + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, it->blurredBackground.width(), it->blurredBackground.height(), 0 , 0, 65535); + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); - - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(-updateRect.x(), -updateRect.height() - updateRect.y(), 0); - shader->setTextureMatrix(textureMatrix); - - vbo->draw(GL_TRIANGLES, horizontalOffset, horizontalCount); - + shader->setTextureSize(it->blurredBackground.size()); + + // Copy the image from this texture + renderTextures[1].bind(); + + renderTargets.last()->attachTexture(it->blurredBackground); + + // Render to the texture attachment of this FrameBufferObject + GLRenderTarget::pushRenderTarget(renderTargets.last()); + vbo->draw(GL_TRIANGLES, (blurRegion.rectCount() * (m_downSampleIterations + 1)) * 6, renderRegion.rectCount() * 6); GLRenderTarget::popRenderTarget(); - tex.unbind(); + + shader->unbind(); + // mark the updated region as valid - it->damagedRegion -= validUpdate; + it->damagedRegion -= validUpdateRegion; } - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - targetTexture.bind(); - - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / targetTexture.height()); - // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { glEnable(GL_BLEND); glBlendColor(0, 0, 0, opacity); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - + + // Render to screen + shader->bind(BlurShader::copySampleType); + it->blurredBackground.bind(); + shader->setModelViewProjectionMatrix(screenProjection); - - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / targetTexture.width(), -1.0 / targetTexture.height(), 1); - textureMatrix.translate(-r.x(), -targetTexture.height() - r.y(), 0); - shader->setTextureMatrix(textureMatrix); - - vbo->draw(GL_TRIANGLES, verticalOffset, verticalCount); - vbo->unbindArrays(); + shader->setTextureSize(screenRect.size()); + shader->setBlurRect(windowRegion.boundingRect(), screenRect.size()); + + vbo->draw(GL_TRIANGLES, (blurRectCount * (m_downSampleIterations + 1)) + renderRegion.rectCount() * 6, windowRegion.rectCount() * 6); if (opacity < 1.0) { glDisable(GL_BLEND); } - - targetTexture.unbind(); + + vbo->unbindArrays(); shader->unbind(); } -int BlurEffect::blurRadius() const -{ - if (!shader) { - return 0; - } - return shader->radius(); -} - } // namespace KWin diff --git a/effects/blur/blur.kcfg b/effects/blur/blur.kcfg --- a/effects/blur/blur.kcfg +++ b/effects/blur/blur.kcfg @@ -5,11 +5,14 @@ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > - - 12 + + 5 true + + false + diff --git a/effects/blur/blur_config.ui b/effects/blur/blur_config.ui --- a/effects/blur/blur_config.ui +++ b/effects/blur/blur_config.ui @@ -6,8 +6,8 @@ 0 0 - 396 - 103 + 480 + 123 @@ -37,28 +37,28 @@ - + Light - + 1 - 14 + 15 - 2 + 1 - 2 + 1 - 12 + 5 Qt::Horizontal @@ -69,7 +69,7 @@ - + Strong @@ -88,6 +88,16 @@ + + + Use simple fullscreen blur + + + false + + + + Qt::Vertical diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h --- a/effects/blur/blurshader.h +++ b/effects/blur/blurshader.h @@ -21,22 +21,15 @@ #define BLURSHADER_H #include +#include +#include + class QMatrix4x4; namespace KWin { -struct KernelValue -{ - KernelValue() {} - KernelValue(float x, float g) : x(x), g(g) {} - bool operator < (const KernelValue &other) const { return x < other.x; } - - float x; - float g; -}; - class BlurShader { public: @@ -49,39 +42,28 @@ return mValid; } - // Sets the radius in pixels - void setRadius(int radius); - int radius() const { - return mRadius; - } - - // Sets the blur direction - void setDirection(Qt::Orientation direction); - Qt::Orientation direction() const { - return mDirection; - } - - // Sets the distance between two pixels - virtual void setPixelDistance(float val) = 0; - virtual void setTextureMatrix(const QMatrix4x4 &matrix) = 0; virtual void setModelViewProjectionMatrix(const QMatrix4x4 &matrix) = 0; + virtual void setOffset(float offset) = 0; + virtual void setTextureSize(QSize textureSize) = 0; + virtual void setBlurRect(QRect blurRect, QSize screenSize) = 0; - virtual void bind() = 0; + virtual void bind(int sampleType) = 0; virtual void unbind() = 0; + + enum sampleTypeEnum { + downSampleType, + upSampleType, + copySampleType + }; protected: - float gaussian(float x, float sigma) const; - QList gaussianKernel() const; void setIsValid(bool value) { mValid = value; } virtual void init() = 0; virtual void reset() = 0; - virtual int maxKernelSize() const = 0; private: - int mRadius; - Qt::Orientation mDirection; bool mValid; }; @@ -96,25 +78,53 @@ GLSLBlurShader(); ~GLSLBlurShader(); - void setPixelDistance(float val); - void bind(); + void bind(int sampleType); void unbind(); - void setTextureMatrix(const QMatrix4x4 &matrix); + void setModelViewProjectionMatrix(const QMatrix4x4 &matrix); + void setOffset(float offset); + void setTextureSize(QSize textureSize); + void setBlurRect(QRect blurRect, QSize screenSize); protected: void init(); void reset(); - int maxKernelSize() const; private: - GLShader *shader; - int mvpMatrixLocation; - int textureMatrixLocation; - int pixelSizeLocation; + GLShader *shaderDownsample; + GLShader *shaderUpsample; + GLShader *shaderCopysample; + + int mvpMatrixLocationDownsample; + int offsetLocationDownsample; + int textureSizeLocationDownsample; + + int mvpMatrixLocationUpsample; + int offsetLocationUpsample; + int textureSizeLocationUpsample; + + int mvpMatrixLocationCopysample; + int textureSizeLocationCopysample; + int blurRectLocationCopysample; + + + //Caching uniform values to aviod unnecessary setUniform calls + int activeSampleType; + + float m_offsetDownsample; + QMatrix4x4 m_matrixDownsample; + QSize m_textureSizeDownsample; + + float m_offsetUpsample; + QMatrix4x4 m_matrixUpsample; + QSize m_textureSizeUpsample; + + QMatrix4x4 m_matrixCopysample; + QSize m_textureSizeCopysample; + QRect m_blurRectCopysample; + }; } // namespace KWin #endif - diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp --- a/effects/blur/blurshader.cpp +++ b/effects/blur/blurshader.cpp @@ -20,6 +20,7 @@ #include "blurshader.h" #include +#include "kwinglutils.h" #include #include @@ -29,11 +30,12 @@ #include + using namespace KWin; BlurShader::BlurShader() - : mRadius(0), mValid(false) + : mValid(false) { } @@ -46,73 +48,14 @@ return new GLSLBlurShader(); } -void BlurShader::setRadius(int radius) -{ - const int r = qMax(radius, 2); - - if (mRadius != r) { - mRadius = r; - reset(); - init(); - } -} - -void BlurShader::setDirection(Qt::Orientation direction) -{ - mDirection = direction; -} - -float BlurShader::gaussian(float x, float sigma) const -{ - return (1.0 / std::sqrt(2.0 * M_PI) * sigma) - * std::exp(-((x * x) / (2.0 * sigma * sigma))); -} - -QList BlurShader::gaussianKernel() const -{ - int size = qMin(mRadius | 1, maxKernelSize()); - if (!(size & 0x1)) - size -= 1; - - QList kernel; - const int center = size / 2; - const qreal sigma = (size - 1) / 2.5; - - kernel << KernelValue(0.0, gaussian(0.0, sigma)); - float total = kernel[0].g; - - for (int x = 1; x <= center; x++) { - const float fx = (x - 1) * 2 + 1.5; - const float g1 = gaussian(fx - 0.5, sigma); - const float g2 = gaussian(fx + 0.5, sigma); - - // Offset taking the contribution of both pixels into account - const float offset = .5 - g1 / (g1 + g2); - - kernel << KernelValue(fx + offset, g1 + g2); - kernel << KernelValue(-(fx + offset), g1 + g2); - - total += (g1 + g2) * 2; - } - - qSort(kernel); - - // Normalize the kernel - for (int i = 0; i < kernel.count(); i++) - kernel[i].g /= total; - - return kernel; -} - - - // ---------------------------------------------------------------------------- GLSLBlurShader::GLSLBlurShader() - : BlurShader(), shader(NULL) + : BlurShader(), shaderDownsample(NULL), shaderUpsample(NULL), shaderCopysample(NULL) { + init(); } GLSLBlurShader::~GLSLBlurShader() @@ -122,185 +65,341 @@ void GLSLBlurShader::reset() { - delete shader; - shader = NULL; + delete shaderDownsample; + shaderDownsample = NULL; + + delete shaderUpsample; + shaderUpsample = NULL; + + delete shaderCopysample; + shaderCopysample = NULL; setIsValid(false); } -void GLSLBlurShader::setPixelDistance(float val) +void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) { if (!isValid()) return; - - QVector2D pixelSize(0.0, 0.0); - if (direction() == Qt::Horizontal) - pixelSize.setX(val); - else - pixelSize.setY(val); - - shader->setUniform(pixelSizeLocation, pixelSize); + + switch (activeSampleType) { + case copySampleType: + if (matrix == m_matrixCopysample) + return; + + m_matrixCopysample = matrix; + shaderCopysample->setUniform(mvpMatrixLocationCopysample, matrix); + break; + + case upSampleType: + if (matrix == m_matrixUpsample) + return; + + m_matrixUpsample = matrix; + shaderUpsample->setUniform(mvpMatrixLocationUpsample, matrix); + break; + + case downSampleType: + if (matrix == m_matrixDownsample) + return; + + m_matrixDownsample = matrix; + shaderDownsample->setUniform(mvpMatrixLocationDownsample, matrix); + break; + } } -void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix) +void GLSLBlurShader::setOffset(float offset) { if (!isValid()) return; - - shader->setUniform(textureMatrixLocation, matrix); + + switch (activeSampleType) { + case upSampleType: + if (offset == m_offsetUpsample) + return; + + m_offsetUpsample = offset; + shaderUpsample->setUniform(offsetLocationUpsample, offset); + break; + + case downSampleType: + if (offset == m_offsetDownsample) + return; + + m_offsetDownsample = offset; + shaderDownsample->setUniform(offsetLocationDownsample, offset); + break; + } } -void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +void GLSLBlurShader::setTextureSize(QSize textureSize) { if (!isValid()) return; + + QVector2D texSize = QVector2D(textureSize.width(), textureSize.height()); + + switch (activeSampleType) { + case copySampleType: + if (textureSize == m_textureSizeCopysample) + return; + + m_textureSizeCopysample = textureSize; + shaderCopysample->setUniform(textureSizeLocationCopysample, texSize); + break; + + case upSampleType: + if (textureSize == m_textureSizeUpsample) + return; + + m_textureSizeUpsample = textureSize; + shaderUpsample->setUniform(textureSizeLocationUpsample, texSize); + break; + + case downSampleType: + if (textureSize == m_textureSizeDownsample) + return; + + m_textureSizeDownsample = textureSize; + shaderDownsample->setUniform(textureSizeLocationDownsample, texSize); + break; + } +} - shader->setUniform(mvpMatrixLocation, matrix); +void GLSLBlurShader::setBlurRect(QRect blurRect, QSize screenSize) +{ + if (!isValid() || blurRect == m_blurRectCopysample) + return; + + m_blurRectCopysample = blurRect; + + QVector4D rect = QVector4D( + blurRect.bottomLeft().x() / float(screenSize.width()), + 1.0 - blurRect.bottomLeft().y() / float(screenSize.height()), + blurRect.topRight().x() / float(screenSize.width()), + 1.0 - blurRect.topRight().y() / float(screenSize.height()) + ); + + shaderCopysample->setUniform(blurRectLocationCopysample, rect); } -void GLSLBlurShader::bind() +void GLSLBlurShader::bind(int sampleType) { if (!isValid()) return; - ShaderManager::instance()->pushShader(shader); + switch (sampleType) { + case copySampleType: + ShaderManager::instance()->pushShader(shaderCopysample); + break; + + case upSampleType: + ShaderManager::instance()->pushShader(shaderUpsample); + break; + + case downSampleType: + ShaderManager::instance()->pushShader(shaderDownsample); + break; + } + + activeSampleType = sampleType; } void GLSLBlurShader::unbind() { ShaderManager::instance()->popShader(); } -int GLSLBlurShader::maxKernelSize() const -{ - if (GLPlatform::instance()->isGLES()) { - // GL_MAX_VARYING_FLOATS not available in GLES - // querying for GL_MAX_VARYING_VECTORS crashes on nouveau - // using the minimum value of 8 - return 8 * 2; - } else { - int value; - glGetIntegerv(GL_MAX_VARYING_FLOATS, &value); - // Maximum number of vec4 varyings * 2 - // The code generator will pack two vec2's into each vec4. - return value / 2; - } -} - void GLSLBlurShader::init() { - QList kernel = gaussianKernel(); - const int size = kernel.size(); - const int center = size / 2; - - QList offsets; - for (int i = 0; i < kernel.size(); i += 2) { - QVector4D vec4(0, 0, 0, 0); - - vec4.setX(kernel[i].x); - vec4.setY(kernel[i].x); - - if (i < kernel.size() - 1) { - vec4.setZ(kernel[i + 1].x); - vec4.setW(kernel[i + 1].x); - } - - offsets << vec4; - } - const bool gles = GLPlatform::instance()->isGLES(); const bool glsl_140 = !gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(1, 40); const bool core = glsl_140 || (gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)); QByteArray vertexSource; - QByteArray fragmentSource; + QByteArray fragmentDownSource; + QByteArray fragmentUpSource; + QByteArray fragmentCopySource; - const QByteArray attribute = core ? "in" : "attribute"; - const QByteArray varying_in = core ? (gles ? "in" : "noperspective in") : "varying"; - const QByteArray varying_out = core ? (gles ? "out" : "noperspective out") : "varying"; - const QByteArray texture2D = core ? "texture" : "texture2D"; - const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; + const QByteArray attribute = core ? "in" : "attribute"; + const QByteArray texture2D = core ? "texture" : "texture2D"; + const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; // Vertex shader // =================================================================== - QTextStream stream(&vertexSource); + QTextStream streamVert(&vertexSource); if (gles) { if (core) { - stream << "#version 300 es\n\n"; + streamVert << "#version 300 es\n\n"; } - stream << "precision highp float;\n"; + + streamVert << "precision highp float;\n"; } else if (glsl_140) { - stream << "#version 140\n\n"; + streamVert << "#version 140\n\n"; } - stream << "uniform mat4 modelViewProjectionMatrix;\n"; - stream << "uniform mat4 textureMatrix;\n"; - stream << "uniform vec2 pixelSize;\n\n"; - stream << attribute << " vec4 vertex;\n\n"; - stream << varying_out << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n"; - stream << "\n"; - stream << "void main(void)\n"; - stream << "{\n"; - stream << " vec4 center = vec4(textureMatrix * vertex).stst;\n"; - stream << " vec4 ps = pixelSize.stst;\n\n"; - for (int i = 0; i < offsets.size(); i++) { - stream << " samplePos[" << i << "] = center + ps * vec4(" - << offsets[i].x() << ", " << offsets[i].y() << ", " - << offsets[i].z() << ", " << offsets[i].w() << ");\n"; + streamVert << "uniform mat4 modelViewProjectionMatrix;\n"; + streamVert << attribute << " vec4 vertex;\n\n"; + streamVert << "\n"; + streamVert << "void main(void)\n"; + streamVert << "{\n"; + streamVert << " gl_Position = modelViewProjectionMatrix * vertex;\n"; + streamVert << "}\n"; + + streamVert.flush(); + + // Fragment shader (Dual Kawase Blur) - Downsample + // =================================================================== + QTextStream streamFragDown(&fragmentDownSource); + + if (gles) { + if (core) { + streamFragDown << "#version 300 es\n\n"; + } + + streamFragDown << "precision highp float;\n"; + } else if (glsl_140) { + streamFragDown << "#version 140\n\n"; } - stream << "\n"; - stream << " gl_Position = modelViewProjectionMatrix * vertex;\n"; - stream << "}\n"; - stream.flush(); - // Fragment shader + streamFragDown << "uniform sampler2D texUnit;\n"; + streamFragDown << "uniform float offset;\n"; + streamFragDown << "uniform vec2 textureSize;\n"; + + if (core) + streamFragDown << "out vec4 fragColor;\n\n"; + + streamFragDown << "void main(void)\n"; + streamFragDown << "{\n"; + streamFragDown << " vec2 uv = vec2(gl_FragCoord.xy / textureSize);\n"; + streamFragDown << " vec2 halfpixel = vec2(0.5 / textureSize);\n"; + streamFragDown << " \n"; + streamFragDown << " vec4 sum = " << texture2D << "(texUnit, uv) * 4.0;\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " \n"; + streamFragDown << " " << fragColor << " = sum / 8.0;\n"; + streamFragDown << "}\n"; + + streamFragDown.flush(); + + // Fragment shader (Dual Kawase Blur) - Upsample // =================================================================== - QTextStream stream2(&fragmentSource); + QTextStream streamFragUp(&fragmentUpSource); if (gles) { if (core) { - stream2 << "#version 300 es\n\n"; + streamFragUp << "#version 300 es\n\n"; } - stream2 << "precision highp float;\n"; + + streamFragUp << "precision highp float;\n"; } else if (glsl_140) { - stream2 << "#version 140\n\n"; + streamFragUp << "#version 140\n\n"; } - stream2 << "uniform sampler2D texUnit;\n"; - stream2 << varying_in << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\n"; + streamFragUp << "uniform sampler2D texUnit;\n"; + streamFragUp << "uniform float offset;\n"; + streamFragUp << "uniform vec2 textureSize;\n"; - for (int i = 0; i <= center; i++) - stream2 << "const float kernel" << i << " = " << kernel[i].g << ";\n"; - stream2 << "\n"; + if (core) + streamFragUp << "out vec4 fragColor;\n\n"; + + streamFragUp << "void main(void)\n"; + streamFragUp << "{\n"; + streamFragUp << " vec2 uv = vec2(gl_FragCoord.xy / textureSize);\n"; + streamFragUp << " vec2 halfpixel = vec2(0.5 / textureSize);\n"; + streamFragUp << " \n"; + streamFragUp << " vec4 sum = " << texture2D << "(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " \n"; + streamFragUp << " " << fragColor << " = sum / 12.0;\n"; + streamFragUp << "}\n"; + + streamFragUp.flush(); + + // Fragment shader - Copy texture + // =================================================================== + QTextStream streamFragCopy(&fragmentCopySource); + + if (gles) { + if (core) { + streamFragCopy << "#version 300 es\n\n"; + } + + streamFragCopy << "precision highp float;\n"; + } else if (glsl_140) { + streamFragCopy << "#version 140\n\n"; + } + + streamFragCopy << "uniform sampler2D texUnit;\n"; + streamFragCopy << "uniform vec2 textureSize;\n"; + streamFragCopy << "uniform vec4 blurRect;\n"; if (core) - stream2 << "out vec4 fragColor;\n\n"; - - stream2 << "void main(void)\n"; - stream2 << "{\n"; - stream2 << " vec4 sum = " << texture2D << "(texUnit, samplePos[0].st) * kernel0;\n"; - for (int i = 1, j = -center + 1; i < size; i++, j++) - stream2 << " sum = sum + " << texture2D << "(texUnit, samplePos[" << i / 2 - << ((i % 2) ? "].pq)" : "].st)") << " * kernel" << center - qAbs(j) << ";\n"; - stream2 << " " << fragColor << " = sum;\n"; - stream2 << "}\n"; - stream2.flush(); - - shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource); - if (shader->isValid()) { - pixelSizeLocation = shader->uniformLocation("pixelSize"); - textureMatrixLocation = shader->uniformLocation("textureMatrix"); - mvpMatrixLocation = shader->uniformLocation("modelViewProjectionMatrix"); + streamFragCopy << "out vec4 fragColor;\n\n"; + + streamFragCopy << "void main(void)\n"; + streamFragCopy << "{\n"; + streamFragCopy << " vec2 uv = vec2(gl_FragCoord.xy / textureSize);\n"; + streamFragCopy << " " << fragColor << " = " << texture2D << "(texUnit, clamp(uv, blurRect.xy, blurRect.zw));\n"; + streamFragCopy << "}\n"; + + streamFragCopy.flush(); + + + + shaderDownsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentDownSource); + shaderUpsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentUpSource); + shaderCopysample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentCopySource); + + bool areShadersValid = shaderDownsample->isValid() && shaderUpsample->isValid() && shaderCopysample->isValid(); + setIsValid(areShadersValid); + + if (areShadersValid) { + mvpMatrixLocationDownsample = shaderDownsample->uniformLocation("modelViewProjectionMatrix"); + offsetLocationDownsample = shaderDownsample->uniformLocation("offset"); + textureSizeLocationDownsample = shaderDownsample->uniformLocation("textureSize"); + + mvpMatrixLocationUpsample = shaderUpsample->uniformLocation("modelViewProjectionMatrix"); + offsetLocationUpsample = shaderUpsample->uniformLocation("offset"); + textureSizeLocationUpsample = shaderUpsample->uniformLocation("textureSize"); + + mvpMatrixLocationCopysample = shaderCopysample->uniformLocation("modelViewProjectionMatrix"); + textureSizeLocationCopysample = shaderCopysample->uniformLocation("textureSize"); + blurRectLocationCopysample = shaderCopysample->uniformLocation("blurRect"); QMatrix4x4 modelViewProjection; const QSize screenSize = effects->virtualScreenSize(); modelViewProjection.ortho(0, screenSize.width(), screenSize.height(), 0, 0, 65535); - ShaderManager::instance()->pushShader(shader); - shader->setUniform(textureMatrixLocation, QMatrix4x4()); - shader->setUniform(mvpMatrixLocation, modelViewProjection); + + //Add default values to the uniforms of the shaders + ShaderManager::instance()->pushShader(shaderDownsample); + shaderDownsample->setUniform(mvpMatrixLocationDownsample, modelViewProjection); + shaderDownsample->setUniform(offsetLocationDownsample, float(1.0)); + shaderDownsample->setUniform(textureSizeLocationDownsample, QVector2D(1.0, 1.0)); + ShaderManager::instance()->popShader(); + + ShaderManager::instance()->pushShader(shaderUpsample); + shaderUpsample->setUniform(mvpMatrixLocationUpsample, modelViewProjection); + shaderUpsample->setUniform(offsetLocationUpsample, float(1.0)); + shaderUpsample->setUniform(textureSizeLocationUpsample, QVector2D(1.0, 1.0)); ShaderManager::instance()->popShader(); + + ShaderManager::instance()->pushShader(shaderCopysample); + shaderCopysample->setUniform(mvpMatrixLocationCopysample, modelViewProjection); + shaderCopysample->setUniform(textureSizeLocationCopysample, QVector2D(1.0, 1.0)); + ShaderManager::instance()->popShader(); + + activeSampleType = -1; } - - setIsValid(shader->isValid()); }