diff --git a/effects/_test/demo_liquid.cpp b/effects/_test/demo_liquid.cpp index 5b1ef0385..be2535b52 100644 --- a/effects/_test/demo_liquid.cpp +++ b/effects/_test/demo_liquid.cpp @@ -1,148 +1,148 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "demo_liquid.h" #include #include #include namespace KWin { KWIN_EFFECT(demo_liquid, LiquidEffect) KWIN_EFFECT_SUPPORTED(demo_liquid, LiquidEffect::supported()) LiquidEffect::LiquidEffect() : Effect() { mTexture = 0; mRenderTarget = 0; mShader = 0; mTime = 0.0f; mValid = loadData(); } LiquidEffect::~LiquidEffect() { delete mTexture; delete mRenderTarget; delete mShader; } bool LiquidEffect::loadData() { // If NPOT textures are not supported, use nearest power-of-two sized // texture. It wastes memory, but it's possible to support systems without // NPOT textures that way int texw = displayWidth(); int texh = displayHeight(); if (!GLTexture::NPOTTextureSupported()) { kWarning(1212) << "NPOT textures not supported, wasting some memory" ; texw = nearestPowerOfTwo(texw); texh = nearestPowerOfTwo(texh); } // Create texture and render target mTexture = new GLTexture(texw, texh); mTexture->setFilter(GL_LINEAR_MIPMAP_LINEAR); mTexture->setWrapMode(GL_CLAMP); - mRenderTarget = new GLRenderTarget(mTexture); + mRenderTarget = new GLRenderTarget(*mTexture); if (!mRenderTarget->valid()) return false; QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/liquid.frag"); QString vertexshader = KGlobal::dirs()->findResource("data", "kwin/liquid.vert"); if (fragmentshader.isEmpty() || vertexshader.isEmpty()) { kError(1212) << "Couldn't locate shader files" << endl; return false; } mShader = new GLShader(vertexshader, fragmentshader); if (!mShader->isValid()) { kError(1212) << "The shader failed to load!" << endl; return false; } mShader->bind(); mShader->setUniform("sceneTex", 0); mShader->setUniform("textureWidth", (float)texw); mShader->setUniform("textureHeight", (float)texh); mShader->unbind(); return true; } bool LiquidEffect::supported() { return GLRenderTarget::supported() && GLShader::fragmentShaderSupported() && (effects->compositingType() == OpenGLCompositing); } void LiquidEffect::prePaintScreen(ScreenPrePaintData& data, int time) { mTime += time / 1000.0f; if (mValid) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; // Start rendering to texture effects->pushRenderTarget(mRenderTarget); } effects->prePaintScreen(data, time); } void LiquidEffect::postPaintScreen() { // Call the next effect. effects->postPaintScreen(); if (mValid) { // Disable render texture assert(effects->popRenderTarget() == mRenderTarget); mTexture->bind(); // Use the shader mShader->bind(); mShader->setUniform("time", (float)mTime); // Render fullscreen quad with screen contents glBegin(GL_QUADS); glVertex2f(0.0, displayHeight()); glVertex2f(displayWidth(), displayHeight()); glVertex2f(displayWidth(), 0.0); glVertex2f(0.0, 0.0); glEnd(); mShader->unbind(); mTexture->unbind(); // Make sure the animation continues effects->addRepaintFull(); } } } // namespace diff --git a/effects/_test/test_fbo.cpp b/effects/_test/test_fbo.cpp index 4cb646ff9..eb45637ec 100644 --- a/effects/_test/test_fbo.cpp +++ b/effects/_test/test_fbo.cpp @@ -1,120 +1,120 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "test_fbo.h" #include #include namespace KWin { KWIN_EFFECT(test_fbo, TestFBOEffect) KWIN_EFFECT_SUPPORTED(test_fbo, TestFBOEffect::supported()) TestFBOEffect::TestFBOEffect() : Effect() { mRot = 0.0f; // Create texture and render target mTexture = new GLTexture(displayWidth(), displayHeight()); mTexture->setFilter(GL_LINEAR_MIPMAP_LINEAR); - mRenderTarget = new GLRenderTarget(mTexture); + mRenderTarget = new GLRenderTarget(*mTexture); mValid = mRenderTarget->valid(); } TestFBOEffect::~TestFBOEffect() { delete mTexture; delete mRenderTarget; } bool TestFBOEffect::supported() { return GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() && (effects->compositingType() == OpenGLCompositing); } void TestFBOEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (mValid) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; // Start rendering to texture effects->pushRenderTarget(mRenderTarget); } effects->prePaintScreen(data, time); } void TestFBOEffect::postPaintScreen() { // Call the next effect. effects->postPaintScreen(); if (mValid) { // Disable render texture assert(effects->popRenderTarget() == mRenderTarget); mTexture->bind(); // Render fullscreen quad with screen contents glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); glEnd(); // Render rotated screen thumbnail mRot += 0.5f; glTranslatef(displayWidth() / 2.0f, displayHeight() / 2.0f, 0.0f); glRotatef(mRot, 0.0, 0.0, 1.0); glScalef(0.2, 0.2, 0.2); glTranslatef(-displayWidth() / 2.0f, -displayHeight() / 2.0f, 0.0f); glEnable(GL_BLEND); glColor4f(1.0, 1.0, 1.0, 0.8); glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); glEnd(); glColor4f(1.0, 1.0, 1.0, 1.0); glDisable(GL_BLEND); // Reset matrix and unbind texture glLoadIdentity(); mTexture->unbind(); // Make sure the animation continues effects->addRepaintFull(); } } } // namespace diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp index ca5a9ecfa..3c4a4b88f 100644 --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -1,415 +1,610 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blur.h" #include "blurshader.h" #include #include #include #include #include namespace KWin { KWIN_EFFECT(blur, BlurEffect) KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported()) KWIN_EFFECT_ENABLEDBYDEFAULT(blur, BlurEffect::enabledByDefault()) BlurEffect::BlurEffect() { shader = BlurShader::create(); // Offscreen texture that's used as the target for the horizontal blur pass // and the source for the vertical pass. - tex = new GLTexture(displayWidth(), displayHeight()); - tex->setFilter(GL_LINEAR); - tex->setWrapMode(GL_CLAMP_TO_EDGE); + tex = GLTexture(displayWidth(), displayHeight()); + tex.setFilter(GL_LINEAR); + tex.setWrapMode(GL_CLAMP_TO_EDGE); target = new GLRenderTarget(tex); net_wm_blur_region = XInternAtom(display(), "_KDE_NET_WM_BLUR_BEHIND_REGION", False); effects->registerPropertyType(net_wm_blur_region, true); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. if (shader->isValid() && target->valid()) { XChangeProperty(display(), rootWindow(), net_wm_blur_region, net_wm_blur_region, 32, PropModeReplace, 0, 0); } else { XDeleteProperty(display(), rootWindow(), net_wm_blur_region); } connect(effects, SIGNAL(windowAdded(EffectWindow*)), this, SLOT(slotWindowAdded(EffectWindow*))); + connect(effects, SIGNAL(windowDeleted(EffectWindow*)), this, SLOT(slotWindowDeleted(EffectWindow*))); connect(effects, SIGNAL(propertyNotify(EffectWindow*,long)), this, SLOT(slotPropertyNotify(EffectWindow*,long))); } BlurEffect::~BlurEffect() { effects->registerPropertyType(net_wm_blur_region, false); XDeleteProperty(display(), rootWindow(), net_wm_blur_region); + windows.clear(); + delete shader; delete target; - delete tex; } void BlurEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) KConfigGroup cg = EffectsHandler::effectConfig("Blur"); int radius = qBound(2, cg.readEntry("BlurRadius", 12), 14); shader->setRadius(radius); + m_shouldCache = cg.readEntry("CacheTexture", true); + + windows.clear(); + if (!shader->isValid()) XDeleteProperty(display(), rootWindow(), net_wm_blur_region); } void BlurEffect::updateBlurRegion(EffectWindow *w) const { QRegion region; const QByteArray value = w->readProperty(net_wm_blur_region, XA_CARDINAL, 32); if (value.size() > 0 && !(value.size() % (4 * sizeof(unsigned long)))) { const unsigned long *cardinals = reinterpret_cast(value.constData()); for (unsigned int i = 0; i < value.size() / sizeof(unsigned long);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; region += QRect(x, y, w, h); } } if (region.isEmpty() && !value.isNull()) { // Set the data to a dummy value. // This is needed to be able to distinguish between the value not // being set, and being set to an empty region. w->setData(WindowBlurBehindRole, 1); } else w->setData(WindowBlurBehindRole, region); } void BlurEffect::slotWindowAdded(EffectWindow *w) { updateBlurRegion(w); } +void BlurEffect::slotWindowDeleted(EffectWindow *w) +{ + if (windows.contains(w)) { + windows.remove(w); + } +} + void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_blur_region) updateBlurRegion(w); } bool BlurEffect::enabledByDefault() { GLPlatform *gl = GLPlatform::instance(); if (gl->isIntel()) return false; return true; } bool BlurEffect::supported() { bool supported = GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() && (GLSLBlurShader::supported() || ARBBlurShader::supported()); if (supported) { int maxTexSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); if (displayWidth() > maxTexSize || displayHeight() > maxTexSize) supported = false; } return supported; } QRect BlurEffect::expand(const QRect &rect) const { const int radius = shader->radius(); return rect.adjusted(-radius, -radius, radius, radius); } QRegion BlurEffect::expand(const QRegion ®ion) const { QRegion expanded; foreach (const QRect & rect, region.rects()) { expanded += expand(rect); } return expanded; } QRegion BlurEffect::blurRegion(const EffectWindow *w) const { QRegion region; const QVariant value = w->data(WindowBlurBehindRole); if (value.isValid()) { const QRegion appRegion = qvariant_cast(value); if (!appRegion.isEmpty()) { if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) { region = w->shape(); region -= w->decorationInnerRect(); region |= appRegion.translated(w->contentsRect().topLeft()) & w->contentsRect(); } else - region = appRegion & w->contentsRect(); + region = appRegion.translated(w->contentsRect().topLeft()) & + w->contentsRect(); } else { // An empty region means that the blur effect should be enabled // for the whole window. region = w->shape(); } } else if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) { // If the client hasn't specified a blur region, we'll only enable // the effect behind the decoration. region = w->shape(); region -= w->decorationInnerRect(); } return region; } void BlurEffect::drawRegion(const QRegion ®ion) { const int vertexCount = region.rectCount() * 6; if (vertices.size() < vertexCount) vertices.resize(vertexCount); int i = 0; foreach (const QRect & r, region.rects()) { vertices[i++] = QVector2D(r.x() + r.width(), r.y()); vertices[i++] = QVector2D(r.x(), r.y()); vertices[i++] = QVector2D(r.x(), r.y() + r.height()); vertices[i++] = QVector2D(r.x(), r.y() + r.height()); vertices[i++] = QVector2D(r.x() + r.width(), r.y() + r.height()); vertices[i++] = QVector2D(r.x() + r.width(), r.y()); } GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(vertexCount, 2, (float*)vertices.constData(), (float*)vertices.constData()); vbo->render(GL_TRIANGLES); } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_damagedArea = QRegion(); + m_paintedArea = QRegion(); m_currentBlur = QRegion(); #ifdef KWIN_HAVE_OPENGLES // HACK: with GLES the screen does not get updated correctly. // as a workaround we trigger full repaints on GLES // we need to find a proper solution or default to blur off on GLES. data.mask |= PAINT_SCREEN_TRANSFORMED; #endif effects->prePaintScreen(data, time); } void BlurEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // this effect relies on prePaintWindow being called in the bottom to top order effects->prePaintWindow(w, data, time); if (!w->isPaintingEnabled()) { return; } // 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(); foreach (const QRect& rect, data.clip.rects()) { newClip |= rect.adjusted(radius,radius,-radius,-radius); } data.clip = newClip; + const QRegion oldPaint = data.paint; + // we don't have to blur a region we don't see m_currentBlur -= newClip; // if we have to paint a non-opaque part of this window that intersects with the // currently blurred region we have to redraw the whole region if ((data.paint-oldClip).intersects(m_currentBlur)) { data.paint |= m_currentBlur; } // TODO: make m_currentBlur a list of connected regions // in case this window has regions to be blurred const QRegion blurArea = blurRegion(w).translated(w->pos()); const QRegion expandedBlur = expand(blurArea); - // if this window or an window underneath the blurred area is damaged we have to - // blur everything - if (m_damagedArea.intersects(blurArea) || data.paint.intersects(blurArea)) { - data.paint |= expandedBlur; - // we have to check again whether we do not damage an already blurred area - if (expandedBlur.intersects(m_currentBlur)) { - data.paint |= m_currentBlur; + + if (m_shouldCache) { + // we are caching the horizontally blurred background texture + + // if a window underneath the blurred area is damaged we have to + // blur everything + if (m_damagedArea.intersects(blurArea)) { + data.paint |= expandedBlur; + // we keep track of the "damage propagation" + m_damagedArea |= expand(blurArea & m_damagedArea); + // 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)) { + data.paint |= m_currentBlur; + } + if (windows.contains(w)) { + windows[w].textureValid = false; + } + + // Normally we would have shrink the clip of the following windows to get a + // full cached copy of the background of this window. But we do not do a full + // cache of the background and rely on the KWin behavior that transformed windows + // are painted with paintGenericScreen. + } + } else { + // we are not caching the window + + // if this window or an window underneath the blurred area is painted again we have to + // blur everything + if (m_paintedArea.intersects(blurArea) || data.paint.intersects(blurArea)) { + data.paint |= expandedBlur; + // we keep track of the "damage propagation" + m_damagedArea |= expand(blurArea & m_damagedArea); + // 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)) { + data.paint |= m_currentBlur; + } } + + m_currentBlur |= expandedBlur; } - m_currentBlur |= expandedBlur; // we don't consider damaged areas which are occluded and are not // explicitly damaged by this window m_damagedArea -= data.clip; - m_damagedArea |= data.paint; + m_damagedArea |= oldPaint; + + // in contrast to m_damagedArea does m_paintedArea keep track of all repainted areas + m_paintedArea -= data.clip; + m_paintedArea |= data.paint; } bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const { if (!target->valid() || !shader->isValid()) return false; if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) return false; if (w->isDesktop()) return false; bool scaled = !qFuzzyCompare(data.xScale, 1.0) && !qFuzzyCompare(data.yScale, 1.0); bool translated = data.xTranslate || data.yTranslate; if (scaled || translated || (mask & PAINT_WINDOW_TRANSFORMED)) return false; bool blurBehindDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind(); if (!w->hasAlpha() && !(blurBehindDecos && w->hasDecoration())) return false; return true; } void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { if (shouldBlur(w, mask, data)) { const QRect screen(0, 0, displayWidth(), displayHeight()); const QRegion shape = region & blurRegion(w).translated(w->pos()) & screen; - if (!shape.isEmpty() && region.intersects(shape.boundingRect())) - doBlur(shape, screen, data.opacity * data.contents_opacity); + if (!shape.isEmpty()) { + if (m_shouldCache) { + doCachedBlur(w, region, data.opacity * data.contents_opacity); + } else { + doBlur(shape, screen, data.opacity * data.contents_opacity); + } + } } // Draw the window over the blurred area effects->drawWindow(w, mask, region, data); } void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) { const QRect screen(0, 0, displayWidth(), displayHeight()); bool valid = target->valid() && shader->isValid(); QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen; if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { doBlur(shape, screen, opacity * frameOpacity); } effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity) { const QRegion expanded = expand(shape) & screen; const QRect r = expanded.boundingRect(); // Create a scratch texture and copy the area in the back buffer that we're // going to blur into it GLTexture scratch(r.width(), r.height()); scratch.setFilter(GL_LINEAR); scratch.setWrapMode(GL_CLAMP_TO_EDGE); scratch.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(), r.width(), r.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()); // Set up the texture matrix to transform from screen coordinates // to texture coordinates. #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_TEXTURE); #endif pushMatrix(); QMatrix4x4 textureMatrix; textureMatrix.scale(1.0 / scratch.width(), -1.0 / scratch.height(), 1); textureMatrix.translate(-r.x(), -scratch.height() - r.y(), 0); loadMatrix(textureMatrix); shader->setTextureMatrix(textureMatrix); drawRegion(expanded); 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(); + tex.bind(); shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / tex->height()); + shader->setPixelDistance(1.0 / tex.height()); // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { #ifndef KWIN_HAVE_OPENGLES glPushAttrib(GL_COLOR_BUFFER_BIT); #endif glEnable(GL_BLEND); glBlendColor(0, 0, 0, opacity); 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); + textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); + textureMatrix.translate(0, -tex.height(), 0); loadMatrix(textureMatrix); shader->setTextureMatrix(textureMatrix); drawRegion(shape); popMatrix(); #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_MODELVIEW); #endif if (opacity < 1.0) { glDisable(GL_BLEND); #ifndef KWIN_HAVE_OPENGLES glPopAttrib(); #endif } - tex->unbind(); + tex.unbind(); + shader->unbind(); +} + +void BlurEffect::doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity) +{ + const QRect screen(0, 0, displayWidth(), displayHeight()); + 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, because we might be + // painting opaque areas top to bottom such that we accidentally blur these areas. + // Anyway this behavior is more performant than shrinking the clipping area of the + // higher windows in order to get a fully valid background texture. + + if (!windows.contains(w)) { + BlurWindowInfo bwi; + bwi.blurredBackground = GLTexture(r.width(),r.height()); + bwi.textureValid = false; + windows[w] = bwi; + } + + if (windows[w].blurredBackground.size() != r.size()) { + windows[w].blurredBackground = GLTexture(r.width(),r.height()); + windows[w].textureValid = false; + } + + GLTexture targetTexture = windows[w].blurredBackground; + targetTexture.setFilter(GL_LINEAR); + targetTexture.setWrapMode(GL_CLAMP_TO_EDGE); + shader->bind(); + QMatrix4x4 textureMatrix; + QMatrix4x4 modelViewProjectionMatrix; +#ifndef KWIN_HAVE_OPENGLES + glMatrixMode(GL_MODELVIEW); + pushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_TEXTURE); + pushMatrix(); + glMatrixMode(GL_PROJECTION); + pushMatrix(); +#endif + + if (!windows[w].textureValid) { + // Create a scratch texture and copy the area in the back buffer that we're + // going to blur into it + GLTexture scratch(r.width(), r.height()); + scratch.setFilter(GL_LINEAR); + scratch.setWrapMode(GL_CLAMP_TO_EDGE); + scratch.bind(); + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(), + r.width(), r.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 / r.width()); + + modelViewProjectionMatrix.ortho(0, r.width(), r.height(), 0 , 0, 65535); + modelViewProjectionMatrix.translate(-r.x(), -r.y(), 0); + loadMatrix(modelViewProjectionMatrix); + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + + // Set up the texture matrix to transform from screen coordinates + // to texture coordinates. + textureMatrix.scale(1.0 / scratch.width(), -1.0 / scratch.height(), 1); + textureMatrix.translate(-r.x(), -scratch.height() - r.y(), 0); +#ifndef KWIN_HAVE_OPENGLES + glMatrixMode(GL_TEXTURE); + loadMatrix(textureMatrix); + glMatrixMode(GL_PROJECTION); +#endif + shader->setTextureMatrix(textureMatrix); + + drawRegion(expanded); + + GLRenderTarget::popRenderTarget(); + scratch.unbind(); + windows[w].textureValid = true; + } + + // 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) { +#ifndef KWIN_HAVE_OPENGLES + glPushAttrib(GL_COLOR_BUFFER_BIT); +#endif + glEnable(GL_BLEND); + glBlendColor(0, 0, 0, opacity); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + } + + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535); + loadMatrix(modelViewProjectionMatrix); + shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + + // 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); +#ifndef KWIN_HAVE_OPENGLES + glMatrixMode(GL_TEXTURE); + loadMatrix(textureMatrix); + glMatrixMode(GL_PROJECTION); +#endif + shader->setTextureMatrix(textureMatrix); + + drawRegion(blurredRegion & region); + +#ifndef KWIN_HAVE_OPENGLES + popMatrix(); + glMatrixMode(GL_TEXTURE); + popMatrix(); + glMatrixMode(GL_MODELVIEW); + popMatrix(); +#endif + + if (opacity < 1.0) { + glDisable(GL_BLEND); +#ifndef KWIN_HAVE_OPENGLES + glPopAttrib(); +#endif + } + + targetTexture.unbind(); shader->unbind(); } } // namespace KWin diff --git a/effects/blur/blur.h b/effects/blur/blur.h index 3c0922b8f..d96f9f605 100644 --- a/effects/blur/blur.h +++ b/effects/blur/blur.h @@ -1,77 +1,88 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLUR_H #define BLUR_H #include #include #include #include #include namespace KWin { class BlurShader; class BlurEffect : public KWin::Effect { Q_OBJECT public: BlurEffect(); ~BlurEffect(); static bool supported(); static bool enabledByDefault(); 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); public Q_SLOTS: void slotWindowAdded(EffectWindow *w); + void slotWindowDeleted(EffectWindow *w); void slotPropertyNotify(EffectWindow *w, long atom); private: 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; void updateBlurRegion(EffectWindow *w) const; void drawRegion(const QRegion ®ion); void doBlur(const QRegion &shape, const QRect &screen, const float opacity); + void doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity); private: BlurShader *shader; QVector vertices; GLRenderTarget *target; - GLTexture *tex; + GLTexture tex; 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 (from bottom to top) + bool m_shouldCache; + + struct BlurWindowInfo { + GLTexture blurredBackground; // keeps the horizontally blurred background + bool textureValid; + }; + + QHash< const EffectWindow*, BlurWindowInfo > windows; }; } // namespace KWin #endif diff --git a/effects/blur/blur_config.cpp b/effects/blur/blur_config.cpp index 0612599b6..6d7c41c20 100644 --- a/effects/blur/blur_config.cpp +++ b/effects/blur/blur_config.cpp @@ -1,76 +1,79 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blur_config.h" #include namespace KWin { KWIN_EFFECT_CONFIG_FACTORY BlurEffectConfig::BlurEffectConfig(QWidget *parent, const QVariantList &args) : KCModule(EffectFactory::componentData(), parent, args) { ui.setupUi(this); connect(ui.slider, SIGNAL(valueChanged(int)), SLOT(valueChanged(int))); + connect(ui.checkBox, SIGNAL(stateChanged(int)), SLOT(valueChanged(int))); load(); } BlurEffectConfig::~BlurEffectConfig() { } void BlurEffectConfig::load() { KCModule::load(); KConfigGroup cg = EffectsHandler::effectConfig("Blur"); ui.slider->setValue(cg.readEntry("BlurRadius", 12) / 2); + ui.checkBox->setChecked(cg.readEntry("CacheTexture", true)); emit changed(false); } void BlurEffectConfig::save() { KCModule::save(); KConfigGroup cg = EffectsHandler::effectConfig("Blur"); cg.writeEntry("BlurRadius", ui.slider->value() * 2); + cg.writeEntry("CacheTexture", ui.checkBox->isChecked()); cg.sync(); emit changed(false); EffectsHandler::sendReloadMessage("blur"); } void BlurEffectConfig::defaults() { emit changed(true); } void BlurEffectConfig::valueChanged(int value) { Q_UNUSED(value) emit changed(true); } } // namespace KWin #include "blur_config.moc" diff --git a/effects/blur/blur_config.ui b/effects/blur/blur_config.ui index 3b99afde4..c55f7045a 100644 --- a/effects/blur/blur_config.ui +++ b/effects/blur/blur_config.ui @@ -1,88 +1,104 @@ BlurEffectConfig 0 0 - 206 - 72 + 396 + 103 Strength of the effect: Qt::Horizontal QSizePolicy::Fixed 20 20 Light 1 7 1 1 Qt::Horizontal QSlider::TicksBelow Strong + + + + + + + Safe intermediate rendering results. + + + Qt::Vertical + + + 0 + 0 + + diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp index decd430e1..18a3e1b64 100644 --- a/effects/blur/blurshader.cpp +++ b/effects/blur/blurshader.cpp @@ -1,477 +1,485 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blurshader.h" #include #include #include #include #include #include #include #include using namespace KWin; BlurShader::BlurShader() : mRadius(0), mValid(false) { } BlurShader::~BlurShader() { } BlurShader *BlurShader::create() { if (GLSLBlurShader::supported()) return new GLSLBlurShader(); return new ARBBlurShader(); } 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))); } QVector BlurShader::gaussianKernel() const { int size = qMin(mRadius | 1, maxKernelSize()); if (!(size & 0x1)) size -= 1; QVector kernel(size); const int center = size / 2; const qreal sigma = (size - 1) / 2.5; // Generate the gaussian kernel kernel[center] = gaussian(0, sigma) * .5; for (int i = 1; i <= center; i++) { const float val = gaussian(1.5 + (i - 1) * 2.0, sigma); kernel[center + i] = val; kernel[center - i] = val; } // Normalize the kernel qreal total = 0; for (int i = 0; i < size; i++) total += kernel[i]; for (int i = 0; i < size; i++) kernel[i] /= total; return kernel; } // ---------------------------------------------------------------------------- GLSLBlurShader::GLSLBlurShader() : BlurShader(), shader(NULL) { } GLSLBlurShader::~GLSLBlurShader() { reset(); } void GLSLBlurShader::reset() { delete shader; shader = NULL; setIsValid(false); } bool GLSLBlurShader::supported() { if (!GLPlatform::instance()->supports(GLSL)) return false; if (!ShaderManager::instance()->isValid()) return false; (void) glGetError(); // Clear the error state #ifndef KWIN_HAVE_OPENGLES // These are the minimum values the implementation is required to support int value = 0; glGetIntegerv(GL_MAX_VARYING_FLOATS, &value); if (value < 32) return false; glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &value); if (value < 64) return false; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &value); if (value < 512) return false; #endif if (glGetError() != GL_NO_ERROR) return false; return true; } void GLSLBlurShader::setPixelDistance(float val) { if (!isValid()) return; QVector2D pixelSize(0.0, 0.0); if (direction() == Qt::Horizontal) pixelSize.setX(val); else pixelSize.setY(val); shader->setUniform("pixelSize", pixelSize); } void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix) { if (!isValid()) { return; } shader->setUniform("u_textureMatrix", matrix); } +void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +{ + if (!isValid()) { + return; + } + shader->setUniform("u_modelViewProjectionMatrix", matrix); +} + void GLSLBlurShader::bind() { if (!isValid()) return; ShaderManager::instance()->pushShader(shader); } void GLSLBlurShader::unbind() { ShaderManager::instance()->popShader(); } int GLSLBlurShader::maxKernelSize() const { #ifdef KWIN_HAVE_OPENGLES // 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; #endif } void GLSLBlurShader::init() { QVector kernel = gaussianKernel(); const int size = kernel.size(); const int center = size / 2; QByteArray vertexSource; QByteArray fragmentSource; // Vertex shader // =================================================================== QTextStream stream(&vertexSource); stream << "uniform mat4 u_modelViewProjectionMatrix;\n"; stream << "uniform mat4 u_textureMatrix;\n"; stream << "uniform vec2 pixelSize;\n\n"; stream << "attribute vec4 vertex;\n"; stream << "attribute vec4 texCoord;\n\n"; stream << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n"; stream << "\n"; stream << "void main(void)\n"; stream << "{\n"; stream << " vec4 center = vec4(u_textureMatrix * texCoord).stst;\n"; stream << " vec4 ps = pixelSize.stst;\n\n"; for (int i = 0; i < size; i += 2) { float offset1, offset2; if (i < center) { offset1 = -(1.5 + (center - i - 1) * 2.0); offset2 = (i + 1) == center ? 0 : offset1 + 2; } else if (i > center) { offset1 = 1.5 + (i - center - 1) * 2.0; offset2 = (i + 1) == size ? 0 : offset1 + 2; } else { offset1 = 0; offset2 = 1.5; } stream << " samplePos[" << i / 2 << "] = center + ps * vec4(" << offset1 << ", " << offset1 << ", " << offset2 << ", " << offset2 << ");\n"; } stream << "\n"; stream << " gl_Position = u_modelViewProjectionMatrix * vertex;\n"; stream << "}\n"; stream.flush(); // Fragment shader // =================================================================== QTextStream stream2(&fragmentSource); stream2 << "uniform sampler2D texUnit;\n"; stream2 << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\n"; for (int i = 0; i <= center; i++) stream2 << "const vec4 kernel" << i << " = vec4(" << kernel[i] << ");\n"; stream2 << "\n"; stream2 << "void main(void)\n"; stream2 << "{\n"; stream2 << " vec4 sum = texture2D(texUnit, samplePos[0].st) * kernel0;\n"; for (int i = 1; i < size; i++) stream2 << " sum = sum + texture2D(texUnit, samplePos[" << i / 2 << ((i % 2) ? "].pq)" : "].st)") << " * kernel" << (i > center ? size - i - 1 : i) << ";\n"; stream2 << " gl_FragColor = sum;\n"; stream2 << "}\n"; stream2.flush(); shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource); if (shader->isValid()) { QMatrix4x4 modelViewProjection; modelViewProjection.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535); ShaderManager::instance()->pushShader(shader); shader->setUniform("texUnit", 0); shader->setUniform("u_textureMatrix", QMatrix4x4()); shader->setUniform("u_modelViewProjectionMatrix", modelViewProjection); ShaderManager::instance()->popShader(); } setIsValid(shader->isValid()); } // ---------------------------------------------------------------------------- ARBBlurShader::ARBBlurShader() : BlurShader(), program(0) { } ARBBlurShader::~ARBBlurShader() { reset(); } void ARBBlurShader::reset() { #ifndef KWIN_HAVE_OPENGLES if (program) { glDeleteProgramsARB(1, &program); program = 0; } setIsValid(false); #endif } bool ARBBlurShader::supported() { #ifdef KWIN_HAVE_OPENGLES return false; #else if (!hasGLExtension("GL_ARB_fragment_program")) return false; (void) glGetError(); // Clear the error state // These are the minimum values the implementation is required to support int value = 0; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value); if (value < 24) return false; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &value); if (value < 16) return false; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value); if (value < 72) return false; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB, &value); if (value < 24) return false; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB, &value); if (value < 4) return false; if (glGetError() != GL_NO_ERROR) return false; return true; #endif } void ARBBlurShader::setPixelDistance(float val) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(val) #else float firstStep = val * 1.5; float nextStep = val * 2.0; if (direction() == Qt::Horizontal) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, firstStep, 0, 0, 0); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, nextStep, 0, 0, 0); } else { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, firstStep, 0, 0); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0, nextStep, 0, 0); } #endif } void ARBBlurShader::bind() { #ifndef KWIN_HAVE_OPENGLES if (!isValid()) return; glEnable(GL_FRAGMENT_PROGRAM_ARB); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program); #endif } void ARBBlurShader::unbind() { #ifndef KWIN_HAVE_OPENGLES int boundObject; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &boundObject); if (boundObject == program) { glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDisable(GL_FRAGMENT_PROGRAM_ARB); } #endif } int ARBBlurShader::maxKernelSize() const { #ifdef KWIN_HAVE_OPENGLES return 0; #else int value; int result; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value); result = (value - 1) * 2; // We only need to store half the kernel, since it's symmetrical glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value); result = qMin(result, value / 3); // We need 3 instructions / sample return result; #endif } void ARBBlurShader::init() { #ifndef KWIN_HAVE_OPENGLES QVector kernel = gaussianKernel(); const int size = kernel.size(); const int center = size / 2; QByteArray text; QTextStream stream(&text); stream << "!!ARBfp1.0\n"; // The kernel values are hardcoded into the program for (int i = 0; i <= center; i++) stream << "PARAM kernel" << i << " = " << kernel[center + i] << ";\n"; stream << "PARAM firstSample = program.local[0];\n"; // Distance from gl_TexCoord[0] to the next sample stream << "PARAM nextSample = program.local[1];\n"; // Distance to the subsequent sample // Temporary variables to hold coordinates and texture samples for (int i = 0; i < size; i++) stream << "TEMP temp" << i << ";\n"; // Compute the texture coordinates stream << "ADD temp1, fragment.texcoord[0], firstSample;\n"; // temp1 = gl_TexCoord[0] + firstSample stream << "SUB temp2, fragment.texcoord[0], firstSample;\n"; // temp2 = gl_TexCoord[0] - firstSample for (int i = 1, j = 3; i < center; i++, j += 2) { stream << "ADD temp" << j + 0 << ", temp" << j - 2 << ", nextSample;\n"; stream << "SUB temp" << j + 1 << ", temp" << j - 1 << ", nextSample;\n"; } // Sample the texture coordinates stream << "TEX temp0, fragment.texcoord[0], texture[0], 2D;\n"; for (int i = 1; i < size; i++) stream << "TEX temp" << i << ", temp" << i << ", texture[0], 2D;\n"; // Multiply the samples with the kernel values and compute the sum stream << "MUL temp0, temp0, kernel0;\n"; for (int i = 0, j = 1; i < center; i++) { stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n"; stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n"; } stream << "MOV result.color, temp0;\n"; // gl_FragColor = temp0 stream << "END\n"; stream.flush(); glGenProgramsARB(1, &program); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program); glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData()); if (glGetError()) { const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); kError() << "Failed to compile fragment program:" << error; setIsValid(false); } else setIsValid(true); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); #endif } diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h index 4308be9f5..ad32bcdc3 100644 --- a/effects/blur/blurshader.h +++ b/effects/blur/blurshader.h @@ -1,135 +1,138 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLURSHADER_H #define BLURSHADER_H #include class QMatrix4x4; namespace KWin { class BlurShader { public: BlurShader(); virtual ~BlurShader(); static BlurShader *create(); bool isValid() const { 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 bind() = 0; virtual void unbind() = 0; protected: float gaussian(float x, float sigma) const; QVector 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; }; // ---------------------------------------------------------------------------- class GLSLBlurShader : public BlurShader { public: GLSLBlurShader(); ~GLSLBlurShader(); void setPixelDistance(float val); void bind(); void unbind(); void setTextureMatrix(const QMatrix4x4 &matrix); + void setModelViewProjectionMatrix(const QMatrix4x4 &matrix); static bool supported(); protected: void init(); void reset(); int maxKernelSize() const; private: GLShader *shader; }; // ---------------------------------------------------------------------------- class ARBBlurShader : public BlurShader { public: ARBBlurShader(); ~ARBBlurShader(); void setPixelDistance(float val); void bind(); void unbind(); - void setTextureMatrix(const QMatrix4x4 &) {}; + void setTextureMatrix(const QMatrix4x4 &) {} + void setModelViewProjectionMatrix(const QMatrix4x4 &matrix) {} static bool supported(); protected: void init(); void reset(); int maxKernelSize() const; private: GLuint program; }; } // namespace KWin #endif diff --git a/effects/logout/logout.cpp b/effects/logout/logout.cpp index cbc429bc9..4e97ebe82 100644 --- a/effects/logout/logout.cpp +++ b/effects/logout/logout.cpp @@ -1,408 +1,408 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2009 Martin Gräßlin Copyright (C) 2009, 2010 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "logout.h" #include "kwinglutils.h" #include #include #include namespace KWin { KWIN_EFFECT(logout, LogoutEffect) LogoutEffect::LogoutEffect() : progress(0.0) , displayEffect(false) , logoutWindow(NULL) , logoutWindowClosed(true) , logoutWindowPassed(false) , canDoPersistent(false) , ignoredWindows() { // Persistent effect logoutAtom = XInternAtom(display(), "_KDE_LOGGING_OUT", False); effects->registerPropertyType(logoutAtom, true); // Block KSMServer's effect char net_wm_cm_name[ 100 ]; sprintf(net_wm_cm_name, "_NET_WM_CM_S%d", DefaultScreen(display())); Atom net_wm_cm = XInternAtom(display(), net_wm_cm_name, False); Window sel = XGetSelectionOwner(display(), net_wm_cm); Atom hack = XInternAtom(display(), "_KWIN_LOGOUT_EFFECT", False); XChangeProperty(display(), sel, hack, hack, 8, PropModeReplace, (unsigned char*)&hack, 1); // the atom is not removed when effect is destroyed, this is temporary anyway #ifdef KWIN_HAVE_OPENGL blurTexture = NULL; blurTarget = NULL; #endif reconfigure(ReconfigureAll); connect(effects, SIGNAL(windowAdded(EffectWindow*)), this, SLOT(slotWindowAdded(EffectWindow*))); connect(effects, SIGNAL(windowClosed(EffectWindow*)), this, SLOT(slotWindowClosed(EffectWindow*))); connect(effects, SIGNAL(windowDeleted(EffectWindow*)), this, SLOT(slotWindowDeleted(EffectWindow*))); connect(effects, SIGNAL(propertyNotify(EffectWindow*,long)), this, SLOT(slotPropertyNotify(EffectWindow*,long))); } LogoutEffect::~LogoutEffect() { #ifdef KWIN_HAVE_OPENGL delete blurTexture; delete blurTarget; #endif } void LogoutEffect::reconfigure(ReconfigureFlags) { #ifdef KWIN_HAVE_OPENGL frameDelay = 0; KConfigGroup conf = effects->effectConfig("Logout"); useBlur = conf.readEntry("UseBlur", true); delete blurTexture; blurTexture = NULL; delete blurTarget; blurTarget = NULL; blurSupported = false; #endif } void LogoutEffect::prePaintScreen(ScreenPrePaintData& data, int time) { #ifdef KWIN_HAVE_OPENGL if (!displayEffect && progress == 0.0) { if (blurTexture) { delete blurTexture; blurTexture = NULL; delete blurTarget; blurTarget = NULL; blurSupported = false; } } else if (!blurTexture) { blurSupported = false; delete blurTarget; // catch as we just tested the texture ;-P if (effects->compositingType() == OpenGLCompositing && GLTexture::NPOTTextureSupported() && useBlur) { // TODO: It seems that it is not possible to create a GLRenderTarget that has // a different size than the display right now. Most likely a KWin core bug. // Create texture and render target blurTexture = new GLTexture(displayWidth(), displayHeight()); blurTexture->setFilter(GL_LINEAR_MIPMAP_LINEAR); blurTexture->setWrapMode(GL_CLAMP_TO_EDGE); - blurTarget = new GLRenderTarget(blurTexture); + blurTarget = new GLRenderTarget(*blurTexture); if (blurTarget->valid()) blurSupported = true; // As creating the render target takes time it can cause the first two frames of the // blur animation to be jerky. For this reason we only start the animation after the // third frame. frameDelay = 2; } } if (frameDelay) --frameDelay; else #endif { if (displayEffect) progress = qMin(1.0, progress + time / animationTime(2000.0)); else if (progress > 0.0) progress = qMax(0.0, progress - time / animationTime(500.0)); } #ifdef KWIN_HAVE_OPENGL if (blurSupported && progress > 0.0) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; } #endif effects->prePaintScreen(data, time); } void LogoutEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (progress > 0.0) { #ifdef KWIN_HAVE_OPENGL if (effects->compositingType() == KWin::OpenGLCompositing) { // In OpenGL mode we add vignetting and, if supported, a slight blur if (blurSupported) { // When using blur we render everything to an FBO and as such don't do the vignetting // until after we render the FBO to the screen. if (w == logoutWindow) { // Window is rendered after the FBO windowOpacity = data.opacity; data.opacity = 0.0; // Cheat, we need the opacity for later but don't want to blur it } else { if (logoutWindowPassed || ignoredWindows.contains(w)) { // Window is rendered after the FBO windows.append(w); windowsOpacities[ w ] = data.opacity; data.opacity = 0.0; } else // Window is added to the FBO data.saturation *= (1.0 - progress * 0.2); } } else { // If we are not blurring then we are not rendering to an FBO if (w == logoutWindow) // This is the logout window don't alter it but render our vignetting now renderVignetting(); else if (!logoutWindowPassed && !ignoredWindows.contains(w)) // Window is in the background, desaturate data.saturation *= (1.0 - progress * 0.2); // All other windows are unaltered } } #endif if (effects->compositingType() == KWin::XRenderCompositing) { // Since we can't do vignetting in XRender just do a stronger desaturation and darken if (w != logoutWindow && !logoutWindowPassed && !ignoredWindows.contains(w)) { data.saturation *= (1.0 - progress * 0.8); data.brightness *= (1.0 - progress * 0.3); } } if (w == logoutWindow || ignoredWindows.contains(w)) // HACK: All windows past the first ignored one should not be // blurred as it affects the stacking order. // All following windows are on top of the logout window and should not be altered either logoutWindowPassed = true; } effects->paintWindow(w, mask, region, data); } void LogoutEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { #ifdef KWIN_HAVE_OPENGL if (blurSupported && progress > 0.0) GLRenderTarget::pushRenderTarget(blurTarget); #endif effects->paintScreen(mask, region, data); #ifdef KWIN_HAVE_OPENGL #ifndef KWIN_HAVE_OPENGLES if (effects->compositingType() == KWin::OpenGLCompositing && progress > 0.0) { if (!blurSupported) { if (!logoutWindowPassed) // The logout window has been deleted but we still want to fade out the vignetting, thus // render it on the top of everything if still animating. We don't check if logoutWindow // is set as it may still be even if it wasn't rendered. renderVignetting(); } else { GLRenderTarget* target = GLRenderTarget::popRenderTarget(); assert(target == blurTarget); Q_UNUSED(target); //-------------------------- // Render the screen effect // HACK: the GL code is still OpenGL 1, so we have to unbind the shader. GLint shader = 0; if (ShaderManager::instance()->isShaderBound()) { glGetIntegerv(GL_CURRENT_PROGRAM, &shader); glUseProgram(0); } glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT); // Unmodified base image blurTexture->bind(); glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); glEnd(); // Blurred image GLfloat bias[1]; glGetTexEnvfv(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, bias); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 1.75); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1.0f, 1.0f, 1.0f, progress * 0.4); glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); glEnd(); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, bias[0]); blurTexture->unbind(); // Vignetting (Radial gradient with transparent middle and black edges) renderVignetting(); glPopAttrib(); // HACK: rebind previously bound shader if (ShaderManager::instance()->isShaderBound()) { glUseProgram(shader); } //-------------------------- // Render the logout window if (logoutWindow) { int winMask = logoutWindow->hasAlpha() ? PAINT_WINDOW_TRANSLUCENT : PAINT_WINDOW_OPAQUE; WindowPaintData winData(logoutWindow); winData.opacity = windowOpacity; effects->drawWindow(logoutWindow, winMask, region, winData); } // Render all windows on top of logout window foreach (EffectWindow * w, windows) { int winMask = w->hasAlpha() ? PAINT_WINDOW_TRANSLUCENT : PAINT_WINDOW_OPAQUE; WindowPaintData winData(w); winData.opacity = windowsOpacities[ w ]; effects->drawWindow(w, winMask, region, winData); } windows.clear(); windowsOpacities.clear(); } } #endif #endif } void LogoutEffect::postPaintScreen() { #ifdef KWIN_HAVE_OPENGL if ((progress != 0.0 && progress != 1.0) || frameDelay) effects->addRepaintFull(); #else if (progress != 0.0 && progress != 1.0) effects->addRepaintFull(); #endif if (progress > 0.0) logoutWindowPassed = false; effects->postPaintScreen(); } void LogoutEffect::slotWindowAdded(EffectWindow* w) { if (isLogoutDialog(w)) { logoutWindow = w; logoutWindowClosed = false; // So we don't blur the window on close progress = 0.0; displayEffect = true; ignoredWindows.clear(); effects->addRepaintFull(); } else if (canDoPersistent) // TODO: Add parent ignoredWindows.append(w); } void LogoutEffect::slotWindowClosed(EffectWindow* w) { if (w == logoutWindow) { logoutWindowClosed = true; if (!canDoPersistent) displayEffect = false; // Fade back to normal effects->addRepaintFull(); } } void LogoutEffect::slotWindowDeleted(EffectWindow* w) { #ifdef KWIN_HAVE_OPENGL windows.removeAll(w); #endif ignoredWindows.removeAll(w); if (w == logoutWindow) logoutWindow = NULL; } bool LogoutEffect::isLogoutDialog(EffectWindow* w) { // TODO there should be probably a better way (window type?) if (w->windowClass() == "ksmserver ksmserver" && (w->windowRole() == "logoutdialog" || w->windowRole() == "logouteffect")) { return true; } return false; } #ifdef KWIN_HAVE_OPENGL void LogoutEffect::renderVignetting() { #ifndef KWIN_HAVE_OPENGLES glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT); glEnable(GL_BLEND); // If not already (Such as when rendered straight to the screen) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (int screen = 0; screen < effects->numScreens(); screen++) { QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); glScissor(screenGeom.x(), displayHeight() - screenGeom.y() - screenGeom.height(), screenGeom.width(), screenGeom.height()); // GL coords are flipped glEnable(GL_SCISSOR_TEST); // Geom must be set before enable const float cenX = screenGeom.x() + screenGeom.width() / 2; const float cenY = screenGeom.y() + screenGeom.height() / 2; const float a = M_PI / 16.0f; // Angle of increment const float r = float((screenGeom.width() > screenGeom.height()) ? screenGeom.width() : screenGeom.height()) * 0.8f; // Radius glBegin(GL_TRIANGLE_FAN); glColor4f(0.0f, 0.0f, 0.0f, 0.0f); glVertex3f(cenX, cenY, 0.0f); glColor4f(0.0f, 0.0f, 0.0f, progress * 0.9f); for (float i = 0.0f; i <= M_PI * 2.01f; i += a) glVertex3f(cenX + r * cos(i), cenY + r * sin(i), 0.0f); glEnd(); glDisable(GL_SCISSOR_TEST); } glPopAttrib(); #endif } #endif void LogoutEffect::slotPropertyNotify(EffectWindow* w, long a) { if (w || a != logoutAtom) return; // Not our atom QByteArray byteData = effects->readRootProperty(logoutAtom, logoutAtom, 8); if (byteData.length() < 1) { // Property was deleted displayEffect = false; return; } // We are using a compatible KSMServer therefore only terminate the effect when the // atom is deleted, not when the dialog is closed. canDoPersistent = true; } bool LogoutEffect::isActive() const { return progress != 0; } } // namespace diff --git a/effects/lookingglass/lookingglass.cpp b/effects/lookingglass/lookingglass.cpp index 1ad78266a..c62e2687e 100644 --- a/effects/lookingglass/lookingglass.cpp +++ b/effects/lookingglass/lookingglass.cpp @@ -1,262 +1,262 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2007 Christian Nitschkowski This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "lookingglass.h" #include #include #include #include #include #include #include #include #include #include #include namespace KWin { KWIN_EFFECT(lookingglass, LookingGlassEffect) KWIN_EFFECT_SUPPORTED(lookingglass, LookingGlassEffect::supported()) LookingGlassEffect::LookingGlassEffect() : zoom(1.0f) , target_zoom(1.0f) , polling(false) , m_texture(NULL) , m_fbo(NULL) , m_vbo(NULL) , m_shader(NULL) , m_enabled(false) , m_valid(false) { actionCollection = new KActionCollection(this); actionCollection->setConfigGlobal(true); actionCollection->setConfigGroup("LookingGlass"); KAction* a; a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ZoomIn, this, SLOT(zoomIn()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Plus)); a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ZoomOut, this, SLOT(zoomOut()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Minus)); a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ActualSize, this, SLOT(toggle()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_0)); connect(effects, SIGNAL(mouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers)), this, SLOT(slotMouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers))); reconfigure(ReconfigureAll); } LookingGlassEffect::~LookingGlassEffect() { delete m_texture; delete m_fbo; delete m_shader; delete m_vbo; } bool LookingGlassEffect::supported() { return GLRenderTarget::supported() && GLPlatform::instance()->supports(GLSL) && (effects->compositingType() == OpenGLCompositing); } void LookingGlassEffect::reconfigure(ReconfigureFlags) { KConfigGroup conf = EffectsHandler::effectConfig("LookingGlass"); initialradius = conf.readEntry("Radius", 200); radius = initialradius; kDebug(1212) << QString("Radius from config: %1").arg(radius) << endl; actionCollection->readSettings(); m_valid = loadData(); } bool LookingGlassEffect::loadData() { // If NPOT textures are not supported, use nearest power-of-two sized // texture. It wastes memory, but it's possible to support systems without // NPOT textures that way int texw = displayWidth(); int texh = displayHeight(); if (!GLTexture::NPOTTextureSupported()) { kWarning(1212) << "NPOT textures not supported, wasting some memory" ; texw = nearestPowerOfTwo(texw); texh = nearestPowerOfTwo(texh); } // Create texture and render target m_texture = new GLTexture(texw, texh); m_texture->setFilter(GL_LINEAR_MIPMAP_LINEAR); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); - m_fbo = new GLRenderTarget(m_texture); + m_fbo = new GLRenderTarget(*m_texture); if (!m_fbo->valid()) { return false; } const QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/lookingglass.frag"); m_shader = ShaderManager::instance()->loadFragmentShader(ShaderManager::SimpleShader, fragmentshader); if (m_shader->isValid()) { ShaderManager::instance()->pushShader(m_shader); m_shader->setUniform("u_textureSize", QVector2D(displayWidth(), displayHeight())); ShaderManager::instance()->popShader(); } else { kError(1212) << "The shader failed to load!" << endl; return false; } m_vbo = new GLVertexBuffer(GLVertexBuffer::Static); QVector verts; QVector texcoords; texcoords << displayWidth() << 0.0; verts << displayWidth() << 0.0; texcoords << 0.0 << 0.0; verts << 0.0 << 0.0; texcoords << 0.0 << displayHeight(); verts << 0.0 << displayHeight(); texcoords << 0.0 << displayHeight(); verts << 0.0 << displayHeight(); texcoords << displayWidth() << displayHeight(); verts << displayWidth() << displayHeight(); texcoords << displayWidth() << 0.0; verts << displayWidth() << 0.0; m_vbo->setData(6, 2, verts.constData(), texcoords.constData()); return true; } void LookingGlassEffect::toggle() { if (target_zoom == 1.0f) { target_zoom = 2.0f; if (!polling) { polling = true; effects->startMousePolling(); } m_enabled = true; } else { target_zoom = 1.0f; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { m_enabled = false; } } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::zoomIn() { target_zoom = qMin(7.0, target_zoom + 0.5); m_enabled = true; if (!polling) { polling = true; effects->startMousePolling(); } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::zoomOut() { target_zoom -= 0.5; if (target_zoom < 1) { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { m_enabled = false; } } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (zoom != target_zoom) { double diff = time / animationTime(500.0); if (target_zoom > zoom) zoom = qMin(zoom * qMax(1.0 + diff, 1.2), target_zoom); else zoom = qMax(zoom * qMin(1.0 - diff, 0.8), target_zoom); kDebug(1212) << "zoom is now " << zoom; radius = qBound((double)initialradius, initialradius * zoom, 3.5 * initialradius); if (zoom <= 1.0f) { m_enabled = false; } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } if (m_valid && m_enabled) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; // Start rendering to texture GLRenderTarget::pushRenderTarget(m_fbo); } effects->prePaintScreen(data, time); } void LookingGlassEffect::slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (pos != old && m_enabled) { effects->addRepaint(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); effects->addRepaint(old.x() - radius, old.y() - radius, 2 * radius, 2 * radius); } } void LookingGlassEffect::postPaintScreen() { // Call the next effect. effects->postPaintScreen(); if (m_valid && m_enabled) { // Disable render texture GLRenderTarget* target = GLRenderTarget::popRenderTarget(); assert(target == m_fbo); Q_UNUSED(target); m_texture->bind(); // Use the shader ShaderManager::instance()->pushShader(m_shader); m_shader->setUniform("u_zoom", (float)zoom); m_shader->setUniform("u_radius", (float)radius); m_shader->setUniform("u_cursor", QVector2D(cursorPos().x(), cursorPos().y())); m_vbo->render(GL_TRIANGLES); ShaderManager::instance()->popShader(); m_texture->unbind(); } } bool LookingGlassEffect::isActive() const { return m_valid && m_enabled; } } // namespace #include "lookingglass.moc" diff --git a/effects/magnifier/magnifier.cpp b/effects/magnifier/magnifier.cpp index 0f7055b9a..0cc4c4e1f 100644 --- a/effects/magnifier/magnifier.cpp +++ b/effects/magnifier/magnifier.cpp @@ -1,248 +1,248 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "magnifier.h" #include #include #include #include #include #include namespace KWin { KWIN_EFFECT(magnifier, MagnifierEffect) KWIN_EFFECT_SUPPORTED(magnifier, MagnifierEffect::supported()) const int FRAME_WIDTH = 5; MagnifierEffect::MagnifierEffect() : zoom(1) , target_zoom(1) , polling(false) , m_texture(0) , m_fbo(0) { KActionCollection* actionCollection = new KActionCollection(this); KAction* a; a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ZoomIn, this, SLOT(zoomIn()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Equal)); a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ZoomOut, this, SLOT(zoomOut()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Minus)); a = static_cast< KAction* >(actionCollection->addAction(KStandardAction::ActualSize, this, SLOT(toggle()))); a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_0)); connect(effects, SIGNAL(mouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers)), this, SLOT(slotMouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers))); reconfigure(ReconfigureAll); } MagnifierEffect::~MagnifierEffect() { delete m_fbo; delete m_texture; } bool MagnifierEffect::supported() { return effects->compositingType() == OpenGLCompositing && GLRenderTarget::blitSupported(); } void MagnifierEffect::reconfigure(ReconfigureFlags) { KConfigGroup conf = EffectsHandler::effectConfig("Magnifier"); int width, height; width = conf.readEntry("Width", 200); height = conf.readEntry("Height", 200); magnifier_size = QSize(width, height); } void MagnifierEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (zoom != target_zoom) { double diff = time / animationTime(500.0); if (target_zoom > zoom) zoom = qMin(zoom * qMax(1 + diff, 1.2), target_zoom); else { zoom = qMax(zoom * qMin(1 - diff, 0.8), target_zoom); if (zoom == 1.0) { // zoom ended - delete FBO and texture delete m_fbo; delete m_texture; m_fbo = NULL; m_texture = NULL; } } } effects->prePaintScreen(data, time); if (zoom != 1.0) data.paint |= magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH); } void MagnifierEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); // paint normal screen if (zoom != 1.0) { // get the right area from the current rendered screen const QRect area = magnifierArea(); const QPoint cursor = cursorPos(); m_fbo->blitFromFramebuffer(QRect(cursor.x() - (double)area.width() / (zoom*2), cursor.y() - (double)area.height() / (zoom*2), (double)area.width() / zoom, (double)area.height() / zoom)); // paint magnifier m_texture->bind(); m_texture->render(infiniteRegion(), area); m_texture->unbind(); QVector verts; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setColor(QColor(0, 0, 0)); // top frame verts << area.right() + FRAME_WIDTH << area.top() - FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.top() - FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.top() - 1; verts << area.left() - FRAME_WIDTH << area.top() - 1; verts << area.right() + FRAME_WIDTH << area.top() - 1; verts << area.right() + FRAME_WIDTH << area.top() - FRAME_WIDTH; // left frame verts << area.left() - 1 << area.top() - FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.top() - FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.left() - 1 << area.bottom() + FRAME_WIDTH; verts << area.left() - 1 << area.top() - FRAME_WIDTH; // right frame verts << area.right() + FRAME_WIDTH << area.top() - FRAME_WIDTH; verts << area.right() + 1 << area.top() - FRAME_WIDTH; verts << area.right() + 1 << area.bottom() + FRAME_WIDTH; verts << area.right() + 1 << area.bottom() + FRAME_WIDTH; verts << area.right() + FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.right() + FRAME_WIDTH << area.top() - FRAME_WIDTH; // bottom frame verts << area.right() + FRAME_WIDTH << area.bottom() + 1; verts << area.left() - FRAME_WIDTH << area.bottom() + 1; verts << area.left() - FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.left() - FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.right() + FRAME_WIDTH << area.bottom() + FRAME_WIDTH; verts << area.right() + FRAME_WIDTH << area.bottom() + 1; vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); if (ShaderManager::instance()->isValid()) { ShaderManager::instance()->pushShader(ShaderManager::ColorShader); } vbo->render(GL_TRIANGLES); if (ShaderManager::instance()->isValid()) { ShaderManager::instance()->popShader(); } } } void MagnifierEffect::postPaintScreen() { if (zoom != target_zoom) { QRect framedarea = magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH); effects->addRepaint(framedarea); } effects->postPaintScreen(); } QRect MagnifierEffect::magnifierArea(QPoint pos) const { return QRect(pos.x() - magnifier_size.width() / 2, pos.y() - magnifier_size.height() / 2, magnifier_size.width(), magnifier_size.height()); } void MagnifierEffect::zoomIn() { target_zoom *= 1.2; if (!polling) { polling = true; effects->startMousePolling(); } if (!m_texture) { m_texture = new GLTexture(magnifier_size); m_texture->setYInverted(false); - m_fbo = new GLRenderTarget(m_texture); + m_fbo = new GLRenderTarget(*m_texture); } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::zoomOut() { target_zoom /= 1.2; if (target_zoom < 1) { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { delete m_fbo; delete m_texture; m_fbo = NULL; m_texture = NULL; } } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::toggle() { if (target_zoom == 1.0) { target_zoom = 2; if (!polling) { polling = true; effects->startMousePolling(); } if (!m_texture) { m_texture = new GLTexture(magnifier_size); m_texture->setYInverted(false); - m_fbo = new GLRenderTarget(m_texture); + m_fbo = new GLRenderTarget(*m_texture); } } else { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (pos != old && zoom != 1) // need full repaint as we might lose some change events on fast mouse movements // see Bug 187658 effects->addRepaintFull(); } bool MagnifierEffect::isActive() const { return zoom != 1.0 || zoom != target_zoom; } } // namespace #include "magnifier.moc" diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 8431c70e8..6b9fcee90 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -1,266 +1,266 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Martin Gräßlin Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "screenshot.h" #include #include #include #include #include #include #include #include namespace KWin { KWIN_EFFECT(screenshot, ScreenShotEffect) KWIN_EFFECT_SUPPORTED(screenshot, ScreenShotEffect::supported()) bool ScreenShotEffect::supported() { return effects->compositingType() == KWin::OpenGLCompositing && GLRenderTarget::supported(); } ScreenShotEffect::ScreenShotEffect() : m_scheduledScreenshot(0) { QDBusConnection::sessionBus().registerObject("/Screenshot", this, QDBusConnection::ExportScriptableContents); QDBusConnection::sessionBus().registerService("org.kde.kwin.Screenshot"); } ScreenShotEffect::~ScreenShotEffect() { QDBusConnection::sessionBus().unregisterObject("/Screenshot"); QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Screenshot"); } void ScreenShotEffect::postPaintScreen() { effects->postPaintScreen(); if (m_scheduledScreenshot) { int w = displayWidth(); int h = displayHeight(); if (!GLTexture::NPOTTextureSupported()) { w = nearestPowerOfTwo(w); h = nearestPowerOfTwo(h); } GLTexture* offscreenTexture = new GLTexture(w, h); offscreenTexture->setFilter(GL_LINEAR); offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); - GLRenderTarget* target = new GLRenderTarget(offscreenTexture); + GLRenderTarget* target = new GLRenderTarget(*offscreenTexture); if (target->valid()) { WindowPaintData d(m_scheduledScreenshot); double left = 0; double top = 0; double right = m_scheduledScreenshot->width(); double bottom = m_scheduledScreenshot->height(); if (m_scheduledScreenshot->hasDecoration() && m_type & INCLUDE_DECORATION) { foreach (const WindowQuad & quad, d.quads) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } else if (m_scheduledScreenshot->hasDecoration()) { WindowQuadList newQuads; left = m_scheduledScreenshot->width(); top = m_scheduledScreenshot->height(); right = 0; bottom = 0; foreach (const WindowQuad & quad, d.quads) { if (quad.type() == WindowQuadContents) { newQuads << quad; left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } d.quads = newQuads; } int width = right - left; int height = bottom - top; d.xTranslate = -m_scheduledScreenshot->x() - left; d.yTranslate = -m_scheduledScreenshot->y() - top; // render window into offscreen texture int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT; GLRenderTarget::pushRenderTarget(target); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0); effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d); // copy content from framebuffer into image QImage img(QSize(width, height), QImage::Format_ARGB32); glReadPixels(0, offscreenTexture->height() - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); GLRenderTarget::popRenderTarget(); ScreenShotEffect::convertFromGLImage(img, width, height); if (m_type & INCLUDE_CURSOR) { grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top); } m_lastScreenshot = QPixmap::fromImage(img); if (m_lastScreenshot.handle() == 0) { Pixmap xpix = XCreatePixmap(display(), rootWindow(), m_lastScreenshot.width(), m_lastScreenshot.height(), 32); m_lastScreenshot = QPixmap::fromX11Pixmap(xpix, QPixmap::ExplicitlyShared); QPainter p(&m_lastScreenshot); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawImage(QPoint(0, 0), img); } emit screenshotCreated(m_lastScreenshot.handle()); } delete offscreenTexture; delete target; m_scheduledScreenshot = NULL; } } void ScreenShotEffect::screenshotWindowUnderCursor(int mask) { m_type = (ScreenShotType)mask; const QPoint cursor = effects->cursorPos(); foreach (EffectWindow * w, effects->stackingOrder()) { if (w->geometry().contains(cursor) && w->isOnCurrentDesktop() && !w->isMinimized()) { m_scheduledScreenshot = w; } } if (m_scheduledScreenshot) { m_scheduledScreenshot->addRepaintFull(); } } QString ScreenShotEffect::screenshotFullscreen() { if (!GLRenderTarget::blitSupported()) { kDebug(1212) << "Framebuffer Blit not supported"; return QString(); } return blitScreenshot(QRect(0, 0, displayWidth(), displayHeight())); } QString ScreenShotEffect::screenshotScreen(int screen) { if (!GLRenderTarget::blitSupported()) { kDebug(1212) << "Framebuffer Blit not supported"; return QString(); } return blitScreenshot(effects->clientArea(FullScreenArea, screen, 0)); } QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height) { if (!GLRenderTarget::blitSupported()) { kDebug(1212) << "Framebuffer Blit not supported"; return QString(); } return blitScreenshot(QRect(x, y, width, height)); } QString ScreenShotEffect::blitScreenshot(const QRect &geometry) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(geometry) return QString(); #else GLTexture tex(geometry.width(), geometry.height()); - GLRenderTarget target(&tex); + GLRenderTarget target(tex); target.blitFromFramebuffer(geometry); // copy content from framebuffer into image tex.bind(); QImage img(geometry.size(), QImage::Format_ARGB32); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); tex.unbind(); ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); KTemporaryFile temp; temp.setSuffix(".png"); temp.setAutoRemove(false); if (!temp.open()) { return QString(); } img.save(&temp); temp.close(); return temp.fileName(); #endif } void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) // Uses the X11_EXTENSIONS_XFIXES_H extension to grab the pointer image, and overlays it onto the snapshot. { XFixesCursorImage *xcursorimg = XFixesGetCursorImage(QX11Info::display()); if (!xcursorimg) return; //Annoyingly, xfixes specifies the data to be 32bit, but places it in an unsigned long * //which can be 64 bit. So we need to iterate over a 64bit structure to put it in a 32bit //structure. QVarLengthArray< quint32 > pixels(xcursorimg->width * xcursorimg->height); for (int i = 0; i < xcursorimg->width * xcursorimg->height; ++i) pixels[i] = xcursorimg->pixels[i] & 0xffffffff; QImage qcursorimg((uchar *) pixels.data(), xcursorimg->width, xcursorimg->height, QImage::Format_ARGB32_Premultiplied); QPainter painter(&snapshot); painter.drawImage(QPointF(xcursorimg->x - xcursorimg->xhot - offsetx, xcursorimg->y - xcursorimg ->yhot - offsety), qcursorimg); XFree(xcursorimg); } void ScreenShotEffect::convertFromGLImage(QImage &img, int w, int h) { // from QtOpenGL/qgl.cpp // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // OpenGL gives RGBA; Qt wants ARGB uint *p = (uint*)img.bits(); uint *end = p + w * h; while (p < end) { uint a = *p << 24; *p = (*p >> 8) | a; p++; } } else { // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB for (int y = 0; y < h; y++) { uint *q = (uint*)img.scanLine(y); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } bool ScreenShotEffect::isActive() const { return m_scheduledScreenshot != NULL; } } // namespace diff --git a/lanczosfilter.cpp b/lanczosfilter.cpp index 7c0bad90c..210b324c7 100644 --- a/lanczosfilter.cpp +++ b/lanczosfilter.cpp @@ -1,674 +1,674 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 by Fredrik Höglund Copyright (C) 2010 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "lanczosfilter.h" #include "effects.h" #include #include #include #include #include #include namespace KWin { LanczosFilter::LanczosFilter(QObject* parent) : QObject(parent) , m_offscreenTex(0) , m_offscreenTarget(0) , m_shader(0) , m_inited(false) { } LanczosFilter::~LanczosFilter() { delete m_offscreenTarget; delete m_offscreenTex; } void LanczosFilter::init() { if (m_inited) return; m_inited = true; const bool force = (qstrcmp(qgetenv("KWIN_FORCE_LANCZOS"), "1") == 0); if (force) { kWarning(1212) << "Lanczos Filter forced on by environment variable"; } KSharedConfigPtr config = KSharedConfig::openConfig("kwinrc"); if (!force && config->group("Compositing").readEntry("GLTextureFilter", 2) != 2) return; // disabled by config // The lanczos filter is reported to be broken with the Intel driver and Mesa 7.10 GLPlatform *gl = GLPlatform::instance(); if (!force && gl->driver() == Driver_Intel && gl->mesaVersion() >= kVersionNumber(7, 10)) return; m_shader = new LanczosShader(this); if (!m_shader->init()) { delete m_shader; m_shader = 0; } } void LanczosFilter::updateOffscreenSurfaces() { int w = displayWidth(); int h = displayHeight(); if (!GLTexture::NPOTTextureSupported()) { w = nearestPowerOfTwo(w); h = nearestPowerOfTwo(h); } if (!m_offscreenTex || m_offscreenTex->width() != w || m_offscreenTex->height() != h) { if (m_offscreenTex) { delete m_offscreenTex; delete m_offscreenTarget; } m_offscreenTex = new GLTexture(w, h); m_offscreenTex->setFilter(GL_LINEAR); m_offscreenTex->setWrapMode(GL_CLAMP_TO_EDGE); - m_offscreenTarget = new GLRenderTarget(m_offscreenTex); + m_offscreenTarget = new GLRenderTarget(*m_offscreenTex); } } static float sinc(float x) { return std::sin(x * M_PI) / (x * M_PI); } static float lanczos(float x, float a) { if (qFuzzyCompare(x + 1.0, 1.0)) return 1.0; if (qAbs(x) >= a) return 0.0; return sinc(x) * sinc(x / a); } void LanczosShader::createKernel(float delta, int *size) { const float a = 2.0; // The two outermost samples always fall at points where the lanczos // function returns 0, so we'll skip them. const int sampleCount = qBound(3, qCeil(delta * a) * 2 + 1 - 2, 29); const int center = sampleCount / 2; const int kernelSize = center + 1; const float factor = 1.0 / delta; QVector values(kernelSize); float sum = 0; for (int i = 0; i < kernelSize; i++) { const float val = lanczos(i * factor, a); sum += i > 0 ? val * 2 : val; values[i] = val; } memset(m_kernel, 0, 16 * sizeof(QVector4D)); // Normalize the kernel for (int i = 0; i < kernelSize; i++) { const float val = values[i] / sum; m_kernel[i] = QVector4D(val, val, val, val); } *size = kernelSize; } void LanczosShader::createOffsets(int count, float width, Qt::Orientation direction) { memset(m_offsets, 0, 16 * sizeof(QVector2D)); for (int i = 0; i < count; i++) { m_offsets[i] = (direction == Qt::Horizontal) ? QVector2D(i / width, 0) : QVector2D(0, i / width); } } void LanczosFilter::performPaint(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (effects->compositingType() == KWin::OpenGLCompositing && (data.xScale < 0.9 || data.yScale < 0.9) && KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) { if (!m_inited) init(); const QRect screenRect = Workspace::self()->clientArea(ScreenArea, w->screen(), w->desktop()); // window geometry may not be bigger than screen geometry to fit into the FBO if (m_shader && w->width() <= screenRect.width() && w->height() <= screenRect.height()) { double left = 0; double top = 0; double right = w->width(); double bottom = w->height(); foreach (const WindowQuad & quad, data.quads) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } double width = right - left; double height = bottom - top; if (width > screenRect.width() || height > screenRect.height()) { // window with padding does not fit into the framebuffer // so cut of the shadow left = 0; top = 0; width = w->width(); height = w->height(); } int tx = data.xTranslate + w->x() + left * data.xScale; int ty = data.yTranslate + w->y() + top * data.yScale; int tw = width * data.xScale; int th = height * data.yScale; const QRect textureRect(tx, ty, tw, th); int sw = width; int sh = height; GLTexture *cachedTexture = static_cast< GLTexture*>(w->data(LanczosCacheRole).value()); if (cachedTexture) { if (cachedTexture->width() == tw && cachedTexture->height() == th) { cachedTexture->bind(); if (ShaderManager::instance()->isValid()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const float rgb = data.brightness * data.opacity; const float a = data.opacity; GLShader *shader = ShaderManager::instance()->pushShader(ShaderManager::SimpleShader); shader->setUniform(GLShader::Offset, QVector2D(0, 0)); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation); shader->setUniform(GLShader::AlphaToOne, 0); cachedTexture->render(textureRect, textureRect); ShaderManager::instance()->popShader(); glDisable(GL_BLEND); } else { prepareRenderStates(cachedTexture, data.opacity, data.brightness, data.saturation); cachedTexture->render(textureRect, textureRect); restoreRenderStates(cachedTexture, data.opacity, data.brightness, data.saturation); } cachedTexture->unbind(); m_timer.start(5000, this); return; } else { // offscreen texture not matching - delete delete cachedTexture; cachedTexture = 0; w->setData(LanczosCacheRole, QVariant()); } } WindowPaintData thumbData = data; thumbData.xScale = 1.0; thumbData.yScale = 1.0; thumbData.xTranslate = -w->x() - left; thumbData.yTranslate = -w->y() - top; thumbData.brightness = 1.0; thumbData.opacity = 1.0; thumbData.saturation = 1.0; // Bind the offscreen FBO and draw the window on it unscaled updateOffscreenSurfaces(); GLRenderTarget::pushRenderTarget(m_offscreenTarget); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); w->sceneWindow()->performPaint(mask, infiniteRegion(), thumbData); // Create a scratch texture and copy the rendered window into it GLTexture tex(sw, sh); tex.setFilter(GL_LINEAR); tex.setWrapMode(GL_CLAMP_TO_EDGE); tex.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, sw, sh); // Set up the shader for horizontal scaling float dx = sw / float(tw); int kernelSize; m_shader->createKernel(dx, &kernelSize); m_shader->createOffsets(kernelSize, sw, Qt::Horizontal); m_shader->bind(); m_shader->setUniforms(); // Draw the window back into the FBO, this time scaled horizontally glClear(GL_COLOR_BUFFER_BIT); QVector verts; QVector texCoords; verts.reserve(12); texCoords.reserve(12); texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right texCoords << 0.0 << 0.0; verts << 0.0 << 0.0; // Top left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 1.0 << 1.0; verts << tw << sh; // Bottom right texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); // At this point we don't need the scratch texture anymore tex.unbind(); tex.discard(); // create scratch texture for second rendering pass GLTexture tex2(tw, sh); tex2.setFilter(GL_LINEAR); tex2.setWrapMode(GL_CLAMP_TO_EDGE); tex2.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, tw, sh); // Set up the shader for vertical scaling float dy = sh / float(th); m_shader->createKernel(dy, &kernelSize); m_shader->createOffsets(kernelSize, m_offscreenTex->height(), Qt::Vertical); m_shader->setUniforms(); // Now draw the horizontally scaled window in the FBO at the right // coordinates on the screen, while scaling it vertically and blending it. glClear(GL_COLOR_BUFFER_BIT); verts.clear(); verts << tw << 0.0; // Top right verts << 0.0 << 0.0; // Top left verts << 0.0 << th; // Bottom left verts << 0.0 << th; // Bottom left verts << tw << th; // Bottom right verts << tw << 0.0; // Top right vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); tex2.unbind(); tex2.discard(); m_shader->unbind(); // create cache texture GLTexture *cache = new GLTexture(tw, th); cache->setFilter(GL_LINEAR); cache->setWrapMode(GL_CLAMP_TO_EDGE); cache->bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - th, tw, th); GLRenderTarget::popRenderTarget(); if (ShaderManager::instance()->isValid()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const float rgb = data.brightness * data.opacity; const float a = data.opacity; GLShader *shader = ShaderManager::instance()->pushShader(ShaderManager::SimpleShader); shader->setUniform(GLShader::Offset, QVector2D(0, 0)); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation); shader->setUniform(GLShader::AlphaToOne, 0); cache->render(textureRect, textureRect); ShaderManager::instance()->popShader(); glDisable(GL_BLEND); } else { prepareRenderStates(cache, data.opacity, data.brightness, data.saturation); cache->render(textureRect, textureRect); restoreRenderStates(cache, data.opacity, data.brightness, data.saturation); } cache->unbind(); w->setData(LanczosCacheRole, QVariant::fromValue(static_cast(cache))); // Delete the offscreen surface after 5 seconds m_timer.start(5000, this); return; } } // if ( effects->compositingType() == KWin::OpenGLCompositing ) w->sceneWindow()->performPaint(mask, region, data); } // End of function void LanczosFilter::timerEvent(QTimerEvent *event) { if (event->timerId() == m_timer.timerId()) { m_timer.stop(); delete m_offscreenTarget; delete m_offscreenTex; m_offscreenTarget = 0; m_offscreenTex = 0; foreach (EffectWindow * w, effects->stackingOrder()) { QVariant cachedTextureVariant = w->data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; cachedTexture = 0; w->setData(LanczosCacheRole, QVariant()); } } } } void LanczosFilter::prepareRenderStates(GLTexture* tex, double opacity, double brightness, double saturation) { #ifndef KWIN_HAVE_OPENGLES const bool alpha = true; // setup blending of transparent windows glPushAttrib(GL_ENABLE_BIT); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); if (saturation != 1.0 && tex->saturationSupported()) { // First we need to get the color from [0; 1] range to [0.5; 1] range glActiveTexture(GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); const float scale_constant[] = { 1.0, 1.0, 1.0, 0.5}; glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, scale_constant); tex->bind(); // Then we take dot product of the result of previous pass and // saturation_constant. This gives us completely unsaturated // (greyscale) image // Note that both operands have to be in range [0.5; 1] since opengl // automatically substracts 0.5 from them glActiveTexture(GL_TEXTURE1); float saturation_constant[] = { 0.5 + 0.5 * 0.30, 0.5 + 0.5 * 0.59, 0.5 + 0.5 * 0.11, saturation }; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant); tex->bind(); // Finally we need to interpolate between the original image and the // greyscale image to get wanted level of saturation glActiveTexture(GL_TEXTURE2); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant); // Also replace alpha by primary color's alpha here glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); // And make primary color contain the wanted opacity glColor4f(opacity, opacity, opacity, opacity); tex->bind(); if (alpha || brightness != 1.0f) { glActiveTexture(GL_TEXTURE3); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // The color has to be multiplied by both opacity and brightness float opacityByBrightness = opacity * brightness; glColor4f(opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity); if (alpha) { // Multiply original texture's alpha by our opacity glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); } else { // Alpha will be taken from previous stage glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); } tex->bind(); } glActiveTexture(GL_TEXTURE0); } else if (opacity != 1.0 || brightness != 1.0) { // the window is additionally configured to have its opacity adjusted, // do it float opacityByBrightness = opacity * brightness; if (alpha) { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glColor4f(opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity); } else { // Multiply color by brightness and replace alpha by opacity float constant[] = { opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity }; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_CONSTANT); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant); } } #endif } void LanczosFilter::restoreRenderStates(GLTexture* tex, double opacity, double brightness, double saturation) { #ifndef KWIN_HAVE_OPENGLES if (opacity != 1.0 || saturation != 1.0 || brightness != 1.0f) { if (saturation != 1.0 && tex->saturationSupported()) { glActiveTexture(GL_TEXTURE3); glDisable(tex->target()); glActiveTexture(GL_TEXTURE2); glDisable(tex->target()); glActiveTexture(GL_TEXTURE1); glDisable(tex->target()); glActiveTexture(GL_TEXTURE0); } } glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glColor4f(0, 0, 0, 0); glPopAttrib(); // ENABLE_BIT #endif } /************************************************ * LanczosShader ************************************************/ LanczosShader::LanczosShader(QObject* parent) : QObject(parent) , m_shader(0) , m_arbProgram(0) { } LanczosShader::~LanczosShader() { delete m_shader; #ifndef KWIN_HAVE_OPENGLES if (m_arbProgram) { glDeleteProgramsARB(1, &m_arbProgram); m_arbProgram = 0; } #endif } void LanczosShader::bind() { if (m_shader) ShaderManager::instance()->pushShader(m_shader); #ifndef KWIN_HAVE_OPENGLES else { glEnable(GL_FRAGMENT_PROGRAM_ARB); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_arbProgram); } #endif } void LanczosShader::unbind() { if (m_shader) ShaderManager::instance()->popShader(); #ifndef KWIN_HAVE_OPENGLES else { int boundObject; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &boundObject); if (boundObject == m_arbProgram) { glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDisable(GL_FRAGMENT_PROGRAM_ARB); } } #endif } void LanczosShader::setUniforms() { if (m_shader) { glUniform1i(m_uTexUnit, 0); glUniform2fv(m_uOffsets, 16, (const GLfloat*)m_offsets); glUniform4fv(m_uKernel, 16, (const GLfloat*)m_kernel); } #ifndef KWIN_HAVE_OPENGLES else { for (int i = 0; i < 16; ++i) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, i, m_offsets[i].x(), m_offsets[i].y(), 0, 0); } for (int i = 0; i < 16; ++i) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, i + 16, m_kernel[i].x(), m_kernel[i].y(), m_kernel[i].z(), m_kernel[i].w()); } } #endif } bool LanczosShader::init() { GLPlatform *gl = GLPlatform::instance(); if (gl->supports(GLSL) && ShaderManager::instance()->isValid() && GLRenderTarget::supported() && !(gl->isRadeon() && gl->chipClass() < R600)) { m_shader = ShaderManager::instance()->loadFragmentShader(ShaderManager::SimpleShader, ":/resources/lanczos-fragment.glsl"); if (m_shader->isValid()) { ShaderManager::instance()->pushShader(m_shader); m_uTexUnit = m_shader->uniformLocation("texUnit"); m_uKernel = m_shader->uniformLocation("kernel"); m_uOffsets = m_shader->uniformLocation("offsets"); ShaderManager::instance()->popShader(); return true; } else { kDebug(1212) << "Shader is not valid"; m_shader = 0; // try ARB shader } } #ifdef KWIN_HAVE_OPENGLES // no ARB shader in GLES return false; #else // try to create an ARB Shader if (!hasGLExtension("GL_ARB_fragment_program")) return false; QByteArray text; QTextStream stream(&text); // Note: This program uses 31 temporaries, 61 ALU instructions, 31 texture // fetches, 3 texture indirections and 93 instructions. // The R300 limitations are 32, 64, 32, 4 and 96 respectively. stream << "!!ARBfp1.0\n"; stream << "TEMP sum;\n"; // Declare 30 temporaries for holding texcoords and TEX results for (int i = 0; i < 30; i++) stream << "TEMP temp" << i << ";\n"; // Compute the texture coordinates for (int i = 0, j = 0; i < 30 / 2; i++) { stream << "ADD temp" << j++ << ", fragment.texcoord, program.local[" << i + 1 << "];\n"; stream << "SUB temp" << j++ << ", fragment.texcoord, program.local[" << i + 1 << "];\n"; } // Sample the texture coordinates stream << "TEX sum, fragment.texcoord, texture[0], 2D;\n"; for (int i = 0; i < 30; i++) stream << "TEX temp" << i << ", temp" << i << ", texture[0], 2D;\n"; // Process the results stream << "MUL sum, sum, program.local[16];\n"; // sum = sum * kernel[0] for (int i = 0, j = 0; i < 30 / 2; i++) { stream << "MAD sum, temp" << j++ << ", program.local[" << (17 + i) << "], sum;\n"; stream << "MAD sum, temp" << j++ << ", program.local[" << (17 + i) << "], sum;\n"; } stream << "MOV result.color, sum;\n"; stream << "END\n"; stream.flush(); glEnable(GL_FRAGMENT_PROGRAM_ARB); glGenProgramsARB(1, &m_arbProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_arbProgram); glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData()); if (glGetError()) { const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); kError() << "Failed to compile fragment program:" << error; glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDeleteProgramsARB(1, &m_arbProgram); glDisable(GL_FRAGMENT_PROGRAM_ARB); m_arbProgram = 0; return false; } glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDisable(GL_FRAGMENT_PROGRAM_ARB); kDebug(1212) << "ARB Shader compiled, id: " << m_arbProgram; return true; #endif } } // namespace diff --git a/libkwineffects/kwinglutils.cpp b/libkwineffects/kwinglutils.cpp index e1d5b7b1c..7e51bc99a 100644 --- a/libkwineffects/kwinglutils.cpp +++ b/libkwineffects/kwinglutils.cpp @@ -1,1323 +1,1348 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006-2007 Rivo Laks Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwinglutils.h" // need to call GLTexturePrivate::initStatic() #include "kwingltexture_p.h" #include "kwinglobals.h" #include "kwineffects.h" #include "kwinglplatform.h" #include "kdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_GLRENDERTARGET 0 #define MAKE_GL_VERSION(major, minor, release) ( ((major) << 16) | ((minor) << 8) | (release) ) namespace KWin { // Variables // GL version, use MAKE_GL_VERSION() macro for comparing with a specific version static int glVersion; // GLX version, use MAKE_GL_VERSION() macro for comparing with a specific version static int glXVersion; // EGL version, use MAKE_GL_VERSION() macro for comparing with a specific version static int eglVersion; // List of all supported GL, EGL and GLX extensions static QStringList glExtensions; static QStringList glxExtensions; static QStringList eglExtension; static bool legacyGl; int glTextureUnitsCount; // Functions void initGLX() { #ifndef KWIN_HAVE_OPENGLES // Get GLX version int major, minor; glXQueryVersion(display(), &major, &minor); glXVersion = MAKE_GL_VERSION(major, minor, 0); // Get list of supported GLX extensions glxExtensions = QString((const char*)glXQueryExtensionsString( display(), DefaultScreen(display()))).split(' '); glxResolveFunctions(); #endif } void initEGL() { #ifdef KWIN_HAVE_OPENGLES EGLDisplay dpy = eglGetCurrentDisplay(); int major, minor; eglInitialize(dpy, &major, &minor); eglVersion = MAKE_GL_VERSION(major, minor, 0); eglExtension = QString((const char*)eglQueryString(dpy, EGL_EXTENSIONS)).split(' '); eglResolveFunctions(); #endif } void initGL() { // Get OpenGL version QString glversionstring = QString((const char*)glGetString(GL_VERSION)); QStringList glversioninfo = glversionstring.left(glversionstring.indexOf(' ')).split('.'); while (glversioninfo.count() < 3) glversioninfo << "0"; #ifdef KWIN_HAVE_OPENGLES legacyGl = false; #else KSharedConfig::Ptr kwinconfig = KSharedConfig::openConfig("kwinrc", KConfig::NoGlobals); KConfigGroup config(kwinconfig, "Compositing"); legacyGl = config.readEntry("GLLegacy", false); glVersion = MAKE_GL_VERSION(glversioninfo[0].toInt(), glversioninfo[1].toInt(), glversioninfo[2].toInt()); #endif // Get list of supported OpenGL extensions glExtensions = QString((const char*)glGetString(GL_EXTENSIONS)).split(' '); // handle OpenGL extensions functions glResolveFunctions(); GLTexturePrivate::initStatic(); GLRenderTarget::initStatic(); GLVertexBuffer::initStatic(); } void cleanupGL() { ShaderManager::cleanup(); } bool hasGLVersion(int major, int minor, int release) { return glVersion >= MAKE_GL_VERSION(major, minor, release); } bool hasGLXVersion(int major, int minor, int release) { return glXVersion >= MAKE_GL_VERSION(major, minor, release); } bool hasEGLVersion(int major, int minor, int release) { return eglVersion >= MAKE_GL_VERSION(major, minor, release); } bool hasGLExtension(const QString& extension) { return glExtensions.contains(extension) || glxExtensions.contains(extension) || eglExtension.contains(extension); } static QString formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return "GL_NO_ERROR"; case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; #ifndef KWIN_HAVE_OPENGLES case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW"; case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW"; #endif case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; default: return QString("0x") + QString::number(err, 16); } } bool checkGLError(const char* txt) { GLenum err = glGetError(); if (err != GL_NO_ERROR) { kWarning(1212) << "GL error (" << txt << "): " << formatGLError(err); return true; } return false; } int nearestPowerOfTwo(int x) { // This method had been copied from Qt's nearest_gl_texture_size() int n = 0, last = 0; for (int s = 0; s < 32; ++s) { if (((x >> s) & 1) == 1) { ++n; last = s; } } if (n > 1) return 1 << (last + 1); return 1 << last; } void pushMatrix() { #ifndef KWIN_HAVE_OPENGLES glPushMatrix(); #endif } void pushMatrix(const QMatrix4x4 &matrix) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(matrix) #else glPushMatrix(); multiplyMatrix(matrix); #endif } void multiplyMatrix(const QMatrix4x4 &matrix) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(matrix) #else GLfloat m[16]; const qreal *data = matrix.constData(); for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { m[i*4+j] = data[i*4+j]; } } glMultMatrixf(m); #endif } void loadMatrix(const QMatrix4x4 &matrix) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(matrix) #else GLfloat m[16]; const qreal *data = matrix.constData(); for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { m[i*4+j] = data[i*4+j]; } } glLoadMatrixf(m); #endif } void popMatrix() { #ifndef KWIN_HAVE_OPENGLES glPopMatrix(); #endif } //**************************************** // GLShader //**************************************** GLShader::GLShader() : mProgram(0) , mValid(false) , mLocationsResolved(false) { } GLShader::GLShader(const QString& vertexfile, const QString& fragmentfile) : mProgram(0) , mValid(false) , mLocationsResolved(false) { loadFromFiles(vertexfile, fragmentfile); } GLShader::~GLShader() { if (mProgram) { glDeleteProgram(mProgram); } } bool GLShader::loadFromFiles(const QString &vertexFile, const QString &fragmentFile) { QFile vf(vertexFile); if (!vf.open(QIODevice::ReadOnly)) { kError(1212) << "Couldn't open" << vertexFile << "for reading!" << endl; return false; } const QByteArray vertexSource = vf.readAll(); QFile ff(fragmentFile); if (!ff.open(QIODevice::ReadOnly)) { kError(1212) << "Couldn't open" << fragmentFile << "for reading!" << endl; return false; } const QByteArray fragmentSource = ff.readAll(); return load(vertexSource, fragmentSource); } bool GLShader::compile(GLuint program, GLenum shaderType, const QByteArray &source) const { GLuint shader = glCreateShader(shaderType); // Prepare the source code QByteArray ba; #ifdef KWIN_HAVE_OPENGLES ba.append("#ifdef GL_ES\nprecision highp float;\n#endif\n"); #endif if (ShaderManager::instance()->isShaderDebug()) { ba.append("#define KWIN_SHADER_DEBUG 1\n"); } ba.append(source); const char* src = ba.constData(); glShaderSource(shader, 1, &src, NULL); // Compile the shader glCompileShader(shader); // Get the shader info log int maxLength, length; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetShaderInfoLog(shader, maxLength, &length, log.data()); // Check the status int status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == 0) { const char *typeName = (shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment"); kError(1212) << "Failed to compile" << typeName << "shader:" << endl << log << endl; } else if (length > 0) kDebug(1212) << "Shader compile log:" << log; if (status != 0) glAttachShader(program, shader); glDeleteShader(shader); return status != 0; } bool GLShader::load(const QByteArray &vertexSource, const QByteArray &fragmentSource) { #ifndef KWIN_HAVE_OPENGLES // Make sure shaders are actually supported if (!GLPlatform::instance()->supports(GLSL) || GLPlatform::instance()->supports(LimitedGLSL)) { kError(1212) << "Shaders are not supported"; return false; } #endif // Create the shader program mProgram = glCreateProgram(); // Compile the vertex shader if (!vertexSource.isEmpty()) { bool success = compile(mProgram, GL_VERTEX_SHADER, vertexSource); if (!success) { glDeleteProgram(mProgram); mProgram = 0; return false; } } // Compile the fragment shader if (!fragmentSource.isEmpty()) { bool success = compile(mProgram, GL_FRAGMENT_SHADER, fragmentSource); if (!success) { glDeleteProgram(mProgram); mProgram = 0; return false; } } glLinkProgram(mProgram); // Get the program info log int maxLength, length; glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetProgramInfoLog(mProgram, maxLength, &length, log.data()); // Make sure the program linked successfully int status; glGetProgramiv(mProgram, GL_LINK_STATUS, &status); if (status == 0) { kError(1212) << "Failed to link shader:" << endl << log << endl; glDeleteProgram(mProgram); mProgram = 0; return false; } else if (length > 0) kDebug(1212) << "Shader link log:" << log; mValid = true; return true; } void GLShader::bind() { glUseProgram(mProgram); } void GLShader::unbind() { glUseProgram(0); } void GLShader::resolveLocations() { if (mLocationsResolved) return; mMatrixLocation[TextureMatrix] = uniformLocation("textureMatrix"); mMatrixLocation[ProjectionMatrix] = uniformLocation("projection"); mMatrixLocation[ModelViewMatrix] = uniformLocation("modelview"); mMatrixLocation[WindowTransformation] = uniformLocation("windowTransformation"); mMatrixLocation[ScreenTransformation] = uniformLocation("screenTransformation"); mVec2Location[Offset] = uniformLocation("offset"); mVec4Location[ModulationConstant] = uniformLocation("modulation"); mFloatLocation[Saturation] = uniformLocation("saturation"); mIntLocation[AlphaToOne] = uniformLocation("u_forceAlpha"); mLocationsResolved = true; } int GLShader::uniformLocation(const char *name) { const int location = glGetUniformLocation(mProgram, name); return location; } bool GLShader::setUniform(GLShader::MatrixUniform uniform, const QMatrix4x4 &matrix) { resolveLocations(); return setUniform(mMatrixLocation[uniform], matrix); } bool GLShader::setUniform(GLShader::Vec2Uniform uniform, const QVector2D &value) { resolveLocations(); return setUniform(mVec2Location[uniform], value); } bool GLShader::setUniform(GLShader::Vec4Uniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mVec4Location[uniform], value); } bool GLShader::setUniform(GLShader::FloatUniform uniform, float value) { resolveLocations(); return setUniform(mFloatLocation[uniform], value); } bool GLShader::setUniform(GLShader::IntUniform uniform, int value) { resolveLocations(); return setUniform(mIntLocation[uniform], value); } bool GLShader::setUniform(const char *name, float value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, int value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector2D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector3D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector4D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QMatrix4x4& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QColor& color) { const int location = uniformLocation(name); return setUniform(location, color); } bool GLShader::setUniform(int location, float value) { if (location >= 0) { glUniform1f(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, int value) { if (location >= 0) { glUniform1i(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector2D &value) { if (location >= 0) { glUniform2fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector3D &value) { if (location >= 0) { glUniform3fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector4D &value) { if (location >= 0) { glUniform4fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QMatrix4x4 &value) { if (location >= 0) { GLfloat m[16]; const qreal *data = value.constData(); // i is column, j is row for m for (int i = 0; i < 16; ++i) { m[i] = data[i]; } glUniformMatrix4fv(location, 1, GL_FALSE, m); } return (location >= 0); } bool GLShader::setUniform(int location, const QColor &color) { if (location >= 0) { glUniform4f(location, color.redF(), color.greenF(), color.blueF(), color.alphaF()); } return (location >= 0); } int GLShader::attributeLocation(const char* name) { int location = glGetAttribLocation(mProgram, name); return location; } bool GLShader::setAttribute(const char* name, float value) { int location = attributeLocation(name); if (location >= 0) { glVertexAttrib1f(location, value); } return (location >= 0); } QMatrix4x4 GLShader::getUniformMatrix4x4(const char* name) { int location = uniformLocation(name); if (location >= 0) { GLfloat m[16]; glGetUniformfv(mProgram, location, m); QMatrix4x4 matrix(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); matrix.optimize(); return matrix; } else { return QMatrix4x4(); } } //**************************************** // ShaderManager //**************************************** ShaderManager *ShaderManager::s_shaderManager = NULL; ShaderManager *ShaderManager::instance() { if (!s_shaderManager) { s_shaderManager = new ShaderManager(); s_shaderManager->initShaders(); s_shaderManager->m_inited = true; } return s_shaderManager; } void ShaderManager::cleanup() { delete s_shaderManager; s_shaderManager = NULL; } ShaderManager::ShaderManager() : m_orthoShader(NULL) , m_genericShader(NULL) , m_colorShader(NULL) , m_inited(false) , m_valid(false) { m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; } ShaderManager::~ShaderManager() { while (!m_boundShaders.isEmpty()) { popShader(); } delete m_orthoShader; delete m_genericShader; delete m_colorShader; } GLShader *ShaderManager::getBoundShader() const { if (m_boundShaders.isEmpty()) { return NULL; } else { return m_boundShaders.top(); } } bool ShaderManager::isShaderBound() const { return !m_boundShaders.isEmpty(); } bool ShaderManager::isValid() const { return m_valid; } bool ShaderManager::isShaderDebug() const { return m_debug; } GLShader *ShaderManager::pushShader(ShaderType type, bool reset) { if (m_inited && !m_valid) { return NULL; } GLShader *shader; switch(type) { case SimpleShader: shader = m_orthoShader; break; case GenericShader: shader = m_genericShader; break; case ColorShader: shader = m_colorShader; break; default: return NULL; } pushShader(shader); if (reset) { resetShader(type); } return shader; } void ShaderManager::pushShader(GLShader *shader) { // only bind shader if it is not already bound if (shader != getBoundShader()) { shader->bind(); } m_boundShaders.push(shader); } void ShaderManager::popShader() { if (m_boundShaders.isEmpty()) { return; } GLShader *shader = m_boundShaders.pop(); if (m_boundShaders.isEmpty()) { // no more shader bound - unbind shader->unbind(); } else if (shader != m_boundShaders.top()) { // only rebind if a different shader is on top of stack m_boundShaders.top()->bind(); } } GLShader *ShaderManager::loadFragmentShader(ShaderType vertex, const QString &fragmentFile) { QString vertexShader; switch(vertex) { case SimpleShader: vertexShader = ":/resources/scene-vertex.glsl"; break; case GenericShader: vertexShader = ":/resources/scene-generic-vertex.glsl"; break; case ColorShader: vertexShader = ":/resources/scene-color-vertex.glsl"; break; } GLShader *shader = new GLShader(vertexShader, fragmentFile); if (shader->isValid()) { pushShader(shader); resetShader(vertex); popShader(); } return shader; } GLShader *ShaderManager::loadVertexShader(ShaderType fragment, const QString &vertexFile) { QString fragmentShader; switch(fragment) { // Simple and Generic Shader use same fragment Shader case SimpleShader: case GenericShader: fragmentShader = ":/resources/scene-fragment.glsl"; break; case ColorShader: fragmentShader = ":/resources/scene-color-fragment.glsl"; break; } GLShader *shader = new GLShader(vertexFile, fragmentShader); if (shader->isValid()) { pushShader(shader); resetShader(fragment); popShader(); } return shader; } GLShader *ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource) { GLShader *shader = new GLShader(); shader->load(vertexSource, fragmentSource); return shader; } void ShaderManager::initShaders() { if (legacyGl) { kDebug(1212) << "OpenGL Shaders disabled by config option"; return; } m_orthoShader = new GLShader(":/resources/scene-vertex.glsl", ":/resources/scene-fragment.glsl"); if (m_orthoShader->isValid()) { pushShader(SimpleShader, true); popShader(); kDebug(1212) << "Ortho Shader is valid"; } else { delete m_orthoShader; m_orthoShader = NULL; kDebug(1212) << "Orho Shader is not valid"; return; } m_genericShader = new GLShader(":/resources/scene-generic-vertex.glsl", ":/resources/scene-fragment.glsl"); if (m_genericShader->isValid()) { pushShader(GenericShader, true); popShader(); kDebug(1212) << "Generic Shader is valid"; } else { delete m_genericShader; m_genericShader = NULL; delete m_orthoShader; m_orthoShader = NULL; kDebug(1212) << "Generic Shader is not valid"; return; } m_colorShader = new GLShader(":/resources/scene-color-vertex.glsl", ":/resources/scene-color-fragment.glsl"); if (m_colorShader->isValid()) { pushShader(ColorShader, true); popShader(); kDebug(1212) << "Color Shader is valid"; } else { delete m_genericShader; m_genericShader = NULL; delete m_orthoShader; m_orthoShader = NULL; delete m_colorShader; m_colorShader = NULL; kDebug(1212) << "Color Scene Shader is not valid"; return; } m_valid = true; } void ShaderManager::resetShader(ShaderType type) { // resetShader is either called from init or from push, we know that a built-in shader is bound const QMatrix4x4 identity; QMatrix4x4 projection; QMatrix4x4 modelView; GLShader *shader = getBoundShader(); switch(type) { case SimpleShader: projection.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535); break; case GenericShader: { // Set up the projection matrix float fovy = 60.0f; float aspect = 1.0f; float zNear = 0.1f; float zFar = 100.0f; float ymax = zNear * tan(fovy * M_PI / 360.0f); float ymin = -ymax; float xmin = ymin * aspect; float xmax = ymax * aspect; projection.frustum(xmin, xmax, ymin, ymax, zNear, zFar); // Set up the model-view matrix float scaleFactor = 1.1 * tan(fovy * M_PI / 360.0f) / ymax; modelView.translate(xmin * scaleFactor, ymax * scaleFactor, -1.1); modelView.scale((xmax - xmin)*scaleFactor / displayWidth(), -(ymax - ymin)*scaleFactor / displayHeight(), 0.001); break; } case ColorShader: projection.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535); shader->setUniform("geometryColor", QVector4D(0, 0, 0, 1)); break; } shader->setUniform("sampler", 0); shader->setUniform(GLShader::ProjectionMatrix, projection); shader->setUniform(GLShader::ModelViewMatrix, modelView); shader->setUniform(GLShader::ScreenTransformation, identity); shader->setUniform(GLShader::WindowTransformation, identity); shader->setUniform(GLShader::Offset, QVector2D(0, 0)); shader->setUniform(GLShader::ModulationConstant, QVector4D(1.0, 1.0, 1.0, 1.0)); shader->setUniform(GLShader::Saturation, 1.0f); shader->setUniform(GLShader::AlphaToOne, 0); } /*** GLRenderTarget ***/ bool GLRenderTarget::sSupported = false; bool GLRenderTarget::s_blitSupported = false; QStack GLRenderTarget::s_renderTargets = QStack(); +QSize GLRenderTarget::s_oldViewport; void GLRenderTarget::initStatic() { #ifdef KWIN_HAVE_OPENGLES sSupported = true; s_blitSupported = false; #else sSupported = hasGLExtension("GL_EXT_framebuffer_object") && glFramebufferTexture2D; s_blitSupported = hasGLExtension("GL_EXT_framebuffer_blit"); #endif } bool GLRenderTarget::isRenderTargetBound() { return !s_renderTargets.isEmpty(); } bool GLRenderTarget::blitSupported() { return s_blitSupported; } void GLRenderTarget::pushRenderTarget(GLRenderTarget* target) { + if (s_renderTargets.isEmpty()) { + GLint params[4]; + glGetIntegerv(GL_VIEWPORT, params); + s_oldViewport = QSize(params[2], params[3]); + } + target->enable(); s_renderTargets.push(target); } GLRenderTarget* GLRenderTarget::popRenderTarget() { GLRenderTarget* ret = s_renderTargets.pop(); ret->disable(); - if (!s_renderTargets.isEmpty()) + if (!s_renderTargets.isEmpty()) { s_renderTargets.top()->enable(); + } else if (!s_oldViewport.isEmpty()) { + glViewport (0, 0, s_oldViewport.width(), s_oldViewport.height()); + } return ret; } -GLRenderTarget::GLRenderTarget(GLTexture* color) +GLRenderTarget::GLRenderTarget(const GLTexture& color) { // Reset variables mValid = false; mTexture = color; // Make sure FBO is supported - if (sSupported && mTexture && !mTexture->isNull()) { + if (sSupported && !mTexture.isNull()) { initFBO(); } else kError(1212) << "Render targets aren't supported!" << endl; } GLRenderTarget::~GLRenderTarget() { if (mValid) { glDeleteFramebuffers(1, &mFramebuffer); } } bool GLRenderTarget::enable() { if (!valid()) { kError(1212) << "Can't enable invalid render target!" << endl; return false; } glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); - mTexture->setDirty(); + glViewport(0, 0, mTexture.width(), mTexture.height()); + mTexture.setDirty(); return true; } bool GLRenderTarget::disable() { if (!valid()) { kError(1212) << "Can't disable invalid render target!" << endl; return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); - mTexture->setDirty(); + mTexture.setDirty(); return true; } static QString formatFramebufferStatus(GLenum status) { switch(status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: // An attachment is the wrong type / is invalid / has 0 width or height return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: // There are no images attached to the framebuffer return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; case GL_FRAMEBUFFER_UNSUPPORTED: // A format or the combination of formats of the attachments is unsupported return "GL_FRAMEBUFFER_UNSUPPORTED"; #ifndef KWIN_HAVE_OPENGLES case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: // Not all attached images have the same width and height return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT"; case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: // The color attachments don't have the same format return "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: // The attachments don't have the same number of samples return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: // The draw buffer is missing return "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: // The read buffer is missing return "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; #endif default: return "Unknown (0x" + QString::number(status, 16) + ')'; } } void GLRenderTarget::initFBO() { #if DEBUG_GLRENDERTARGET GLenum err = glGetError(); if (err != GL_NO_ERROR) kError(1212) << "Error status when entering GLRenderTarget::initFBO: " << formatGLError(err); #endif glGenFramebuffers(1, &mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { kError(1212) << "glGenFramebuffers failed: " << formatGLError(err); return; } #endif glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { kError(1212) << "glBindFramebuffer failed: " << formatGLError(err); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - mTexture->target(), mTexture->texture(), 0); + mTexture.target(), mTexture.texture(), 0); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { kError(1212) << "glFramebufferTexture2D failed: " << formatGLError(err); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) { // We have an incomplete framebuffer, consider it invalid if (status == 0) kError(1212) << "glCheckFramebufferStatus failed: " << formatGLError(glGetError()); else kError(1212) << "Invalid framebuffer status: " << formatFramebufferStatus(status); glDeleteFramebuffers(1, &mFramebuffer); return; } mValid = true; } void GLRenderTarget::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter) { if (!GLRenderTarget::blitSupported()) { return; } #ifndef KWIN_HAVE_OPENGLES GLRenderTarget::pushRenderTarget(this); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); const QRect s = source.isNull() ? QRect(0, 0, displayWidth(), displayHeight()) : source; - const QRect d = destination.isNull() ? QRect(0, 0, mTexture->width(), mTexture->height()) : destination; + const QRect d = destination.isNull() ? QRect(0, 0, mTexture.width(), mTexture.height()) : destination; glBlitFramebuffer(s.x(), displayHeight() - s.y() - s.height(), s.x() + s.width(), displayHeight() - s.y(), - d.x(), mTexture->height() - d.y() - d.height(), d.x() + d.width(), mTexture->height() - d.y(), + d.x(), mTexture.height() - d.y() - d.height(), d.x() + d.width(), mTexture.height() - d.y(), GL_COLOR_BUFFER_BIT, filter); GLRenderTarget::popRenderTarget(); #endif } +void GLRenderTarget::attachTexture(const GLTexture& target) +{ + if (!mValid || mTexture.texture() == target.texture()) { + return; + } + + pushRenderTarget(this); + + mTexture = target; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + mTexture.target(), mTexture.texture(), 0); + + popRenderTarget(); +} //********************************* // GLVertexBufferPrivate //********************************* class GLVertexBufferPrivate { public: GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint) : hint(usageHint) , numberVertices(0) , dimension(2) , useColor(false) , useTexCoords(true) , color(0, 0, 0, 255) { if (GLVertexBufferPrivate::supported) { glGenBuffers(2, buffers); } } ~GLVertexBufferPrivate() { if (GLVertexBufferPrivate::supported) { glDeleteBuffers(2, buffers); } } GLVertexBuffer::UsageHint hint; GLuint buffers[2]; int numberVertices; int dimension; static bool supported; static GLVertexBuffer *streamingBuffer; QVector legacyVertices; QVector legacyTexCoords; bool useColor; bool useTexCoords; QColor color; //! VBO is not supported void legacyPainting(QRegion region, GLenum primitiveMode); //! VBO and shaders are both supported void corePainting(const QRegion& region, GLenum primitiveMode); //! VBO is supported, but shaders are not supported void fallbackPainting(const QRegion& region, GLenum primitiveMode); }; bool GLVertexBufferPrivate::supported = false; GLVertexBuffer *GLVertexBufferPrivate::streamingBuffer = NULL; void GLVertexBufferPrivate::legacyPainting(QRegion region, GLenum primitiveMode) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(primitiveMode) #else Q_UNUSED(region) // Enable arrays glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(dimension, GL_FLOAT, 0, legacyVertices.constData()); if (!legacyTexCoords.isEmpty()) { glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, legacyTexCoords.constData()); } if (useColor) { glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } glDrawArrays(primitiveMode, 0, numberVertices); glDisableClientState(GL_VERTEX_ARRAY); if (!legacyTexCoords.isEmpty()) { glDisableClientState(GL_TEXTURE_COORD_ARRAY); } #endif } void GLVertexBufferPrivate::corePainting(const QRegion& region, GLenum primitiveMode) { Q_UNUSED(region) GLShader *shader = ShaderManager::instance()->getBoundShader(); GLint vertexAttrib = shader->attributeLocation("vertex"); GLint texAttrib = shader->attributeLocation("texCoord"); glEnableVertexAttribArray(vertexAttrib); if (useTexCoords) { glEnableVertexAttribArray(texAttrib); } if (useColor) { shader->setUniform("geometryColor", color); } glBindBuffer(GL_ARRAY_BUFFER, buffers[ 0 ]); glVertexAttribPointer(vertexAttrib, dimension, GL_FLOAT, GL_FALSE, 0, 0); if (texAttrib != -1 && useTexCoords) { glBindBuffer(GL_ARRAY_BUFFER, buffers[ 1 ]); glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0); } glDrawArrays(primitiveMode, 0, numberVertices); glBindBuffer(GL_ARRAY_BUFFER, 0); if (useTexCoords) { glDisableVertexAttribArray(texAttrib); } glDisableVertexAttribArray(vertexAttrib); } void GLVertexBufferPrivate::fallbackPainting(const QRegion& region, GLenum primitiveMode) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(primitiveMode) #else Q_UNUSED(region) glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, buffers[ 0 ]); glVertexPointer(dimension, GL_FLOAT, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, buffers[ 1 ]); glTexCoordPointer(2, GL_FLOAT, 0, 0); if (useColor) { glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } // Clip using scissoring glDrawArrays(primitiveMode, 0, numberVertices); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); #endif } //********************************* // GLVertexBuffer //********************************* GLVertexBuffer::GLVertexBuffer(UsageHint hint) : d(new GLVertexBufferPrivate(hint)) { } GLVertexBuffer::~GLVertexBuffer() { delete d; } void GLVertexBuffer::setData(int numberVertices, int dim, const float* vertices, const float* texcoords) { d->numberVertices = numberVertices; d->dimension = dim; d->useTexCoords = (texcoords != NULL); if (!GLVertexBufferPrivate::supported) { // legacy data d->legacyVertices.clear(); d->legacyVertices.reserve(numberVertices * dim); for (int i = 0; i < numberVertices * dim; ++i) { d->legacyVertices << vertices[i]; } d->legacyTexCoords.clear(); if (d->useTexCoords) { d->legacyTexCoords.reserve(numberVertices * 2); for (int i = 0; i < numberVertices * 2; ++i) { d->legacyTexCoords << texcoords[i]; } } return; } GLenum hint; switch(d->hint) { case Dynamic: hint = GL_DYNAMIC_DRAW; break; case Static: hint = GL_STATIC_DRAW; break; case Stream: hint = GL_STREAM_DRAW; break; default: // just to make the compiler happy hint = GL_STREAM_DRAW; break; } glBindBuffer(GL_ARRAY_BUFFER, d->buffers[ 0 ]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*numberVertices * d->dimension, vertices, hint); if (d->useTexCoords) { glBindBuffer(GL_ARRAY_BUFFER, d->buffers[ 1 ]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*numberVertices * 2, texcoords, hint); } glBindBuffer(GL_ARRAY_BUFFER, 0); } void GLVertexBuffer::render(GLenum primitiveMode) { render(infiniteRegion(), primitiveMode); } void GLVertexBuffer::render(const QRegion& region, GLenum primitiveMode) { if (!GLVertexBufferPrivate::supported) { d->legacyPainting(region, primitiveMode); } else if (ShaderManager::instance()->isShaderBound()) { d->corePainting(region, primitiveMode); } else { d->fallbackPainting(region, primitiveMode); } } bool GLVertexBuffer::isSupported() { return GLVertexBufferPrivate::supported; } bool GLVertexBuffer::isUseColor() const { return d->useColor; } void GLVertexBuffer::setUseColor(bool enable) { d->useColor = enable; } void GLVertexBuffer::setColor(const QColor& color, bool enable) { d->useColor = enable; d->color = color; } void GLVertexBuffer::reset() { d->useColor = false; d->color = QColor(0, 0, 0, 255); d->numberVertices = 0; d->dimension = 2; d->useTexCoords = true; } void GLVertexBuffer::initStatic() { #ifdef KWIN_HAVE_OPENGLES GLVertexBufferPrivate::supported = true; #else GLVertexBufferPrivate::supported = hasGLExtension("GL_ARB_vertex_buffer_object"); #endif GLVertexBufferPrivate::streamingBuffer = new GLVertexBuffer(GLVertexBuffer::Stream); } GLVertexBuffer *GLVertexBuffer::streamingBuffer() { return GLVertexBufferPrivate::streamingBuffer; } } // namespace diff --git a/libkwineffects/kwinglutils.h b/libkwineffects/kwinglutils.h index 59bac2e7f..876cf3ad7 100644 --- a/libkwineffects/kwinglutils.h +++ b/libkwineffects/kwinglutils.h @@ -1,561 +1,569 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006-2007 Rivo Laks Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_GLUTILS_H #define KWIN_GLUTILS_H #include #include #include #include #include #include #include "kwingltexture.h" /** @addtogroup kwineffects */ /** @{ */ class QVector2D; class QVector3D; class QVector4D; class QMatrix4x4; template< class K, class V > class QHash; namespace KWin { class GLTexture; class GLVertexBuffer; class GLVertexBufferPrivate; // Initializes GLX function pointers void KWIN_EXPORT initGLX(); // Initializes OpenGL stuff. This includes resolving function pointers as // well as checking for GL version and extensions // Note that GL context has to be created by the time this function is called void KWIN_EXPORT initGL(); // Initializes EGL function pointers void KWIN_EXPORT initEGL(); // Cleans up all resources hold by the GL Context void KWIN_EXPORT cleanupGL(); // Number of supported texture units extern KWIN_EXPORT int glTextureUnitsCount; bool KWIN_EXPORT hasGLVersion(int major, int minor, int release = 0); bool KWIN_EXPORT hasGLXVersion(int major, int minor, int release = 0); bool KWIN_EXPORT hasEGLVersion(int major, int minor, int release = 0); // use for both OpenGL and GLX extensions bool KWIN_EXPORT hasGLExtension(const QString& extension); // detect OpenGL error (add to various places in code to pinpoint the place) bool KWIN_EXPORT checkGLError(const char* txt); inline bool KWIN_EXPORT isPowerOfTwo(int x) { return ((x & (x - 1)) == 0); } /** * @return power of two integer _greater or equal to_ x. * E.g. nearestPowerOfTwo(513) = nearestPowerOfTwo(800) = 1024 **/ int KWIN_EXPORT nearestPowerOfTwo(int x); /** * Push a new matrix on the GL matrix stack. * In GLES this method is a noop. This method should be preferred over glPushMatrix * as it also handles GLES. * @since 4.7 **/ KWIN_EXPORT void pushMatrix(); /** * Multiplies current matrix on GL stack with @p matrix and pushes the result on the matrix stack. * This method is the same as pushMatrix followed by multiplyMatrix. * In GLES this method is a noop. This method should be preferred over glPushMatrix * as it also handles GLES. * @param matrix The matrix the current matrix on the stack should be multiplied with. * @see pushMatrix * @see multiplyMatrix * @since 4.7 **/ KWIN_EXPORT void pushMatrix(const QMatrix4x4 &matrix); /** * Multiplies the current matrix on GL stack with @p matrix. * In GLES this method is a noop. This method should be preferred over glMultMatrix * as it also handles GLES. * @param matrix The matrix the current matrix on the stack should be multiplied with. * @since 4.7 **/ KWIN_EXPORT void multiplyMatrix(const QMatrix4x4 &matrix); /** * Replaces the current matrix on GL stack with @p matrix. * In GLES this method is a no-op. This method should be preferred over glLoadMatrix * as it also handles GLES. * @param matrix The new matrix to replace the existing one on the GL stack. * @since 4.7 **/ KWIN_EXPORT void loadMatrix(const QMatrix4x4 &matrix); /** * Pops the current matrix from the GL matrix stack. * In GLES this method is a noop. This method should be preferred over glPopMatrix * as it also handles GLES. * @since 4.7 **/ KWIN_EXPORT void popMatrix(); class KWIN_EXPORT GLShader { public: GLShader(const QString& vertexfile, const QString& fragmentfile); ~GLShader(); bool isValid() const { return mValid; } int uniformLocation(const char* name); bool setUniform(const char* name, float value); bool setUniform(const char* name, int value); bool setUniform(const char* name, const QVector2D& value); bool setUniform(const char* name, const QVector3D& value); bool setUniform(const char* name, const QVector4D& value); bool setUniform(const char* name, const QMatrix4x4& value); bool setUniform(const char* name, const QColor& color); bool setUniform(int location, float value); bool setUniform(int location, int value); bool setUniform(int location, const QVector2D &value); bool setUniform(int location, const QVector3D &value); bool setUniform(int location, const QVector4D &value); bool setUniform(int location, const QMatrix4x4 &value); bool setUniform(int location, const QColor &value); int attributeLocation(const char* name); bool setAttribute(const char* name, float value); /** * @return The value of the uniform as a matrix * @since 4.7 **/ QMatrix4x4 getUniformMatrix4x4(const char* name); enum MatrixUniform { TextureMatrix = 0, ProjectionMatrix, ModelViewMatrix, WindowTransformation, ScreenTransformation, MatrixCount }; enum Vec2Uniform { Offset, Vec2UniformCount }; enum Vec4Uniform { ModulationConstant, Vec4UniformCount }; enum FloatUniform { Saturation, FloatUniformCount }; enum IntUniform { AlphaToOne, IntUniformCount }; bool setUniform(MatrixUniform uniform, const QMatrix4x4 &matrix); bool setUniform(Vec2Uniform uniform, const QVector2D &value); bool setUniform(Vec4Uniform uniform, const QVector4D &value); bool setUniform(FloatUniform uniform, float value); bool setUniform(IntUniform uniform, int value); protected: GLShader(); bool loadFromFiles(const QString& vertexfile, const QString& fragmentfile); bool load(const QByteArray &vertexSource, const QByteArray &fragmentSource); bool compile(GLuint program, GLenum shaderType, const QByteArray &sourceCode) const; void bind(); void unbind(); void resolveLocations(); private: unsigned int mProgram; bool mValid:1; bool mLocationsResolved:1; int mMatrixLocation[MatrixCount]; int mVec2Location[Vec2UniformCount]; int mVec4Location[Vec4UniformCount]; int mFloatLocation[FloatUniformCount]; int mIntLocation[IntUniformCount]; friend class ShaderManager; }; /** * @short Manager for Shaders. * * This class provides some built-in shaders to be used by both compositing scene and effects. * The ShaderManager provides methods to bind a built-in or a custom shader and keeps track of * the shaders which have been bound. When a shader is unbound the previously bound shader * will be rebound. * * @author Martin Gräßlin * @since 4.7 **/ class KWIN_EXPORT ShaderManager { public: /** * Identifiers for built-in shaders available for effects and scene **/ enum ShaderType { /** * An orthographic projection shader able to render textured geometries. * Expects a @c vec2 uniform @c offset describing the offset from top-left corner * and defaults to @c (0/0). Expects a @c vec2 uniform @c textureSize to calculate * normalized texture coordinates. Defaults to @c (1.0/1.0). And expects a @c vec3 * uniform @c colorManiuplation, with @c x being opacity, @c y being brightness and * @c z being saturation. All three values default to @c 1.0. * The fragment shader takes a uniform @c u_forceAlpha to force the alpha component * to @c 1.0 if it is set to a value not @c 0. This uniform is useful when rendering * a RGB-only window and the alpha channel is required in a later step. * The sampler uniform is @c sample and defaults to @c 0. * The shader uses two vertex attributes @c vertex and @c texCoord. **/ SimpleShader, /** * A generic shader able to render transformed, textured geometries. * This shader is mostly needed by the scene and not of much interest for effects. * Effects can influence this shader through @link ScreenPaintData and @link WindowPaintData. * The shader expects four @c mat4 uniforms @c projection, @c modelview, * @c screenTransformation and @c windowTransformation. The fragment shader expect the * same uniforms as the SimpleShader and the same vertex attributes are used. **/ GenericShader, /** * An orthographic shader to render simple colored geometries without texturing. * Expects a @c vec2 uniform @c offset describing the offset from top-left corner * and defaults to @c (0/0). The fragment shader expects a single @c vec4 uniform * @c geometryColor, which defaults to fully opaque black. * The Shader uses one vertex attribute @c vertex. **/ ColorShader }; /** * @return The currently bound shader or @c null if no shader is bound. **/ GLShader *getBoundShader() const; /** * @return @c true if a shader is bound, @c false otherwise **/ bool isShaderBound() const; /** * @return @c true if the built-in shaders are valid, @c false otherwise **/ bool isValid() const; /** * Is @c true if the environment variable KWIN_GL_DEBUG is set to 1. * In that case shaders are compiled with KWIN_SHADER_DEBUG defined. * @returns @c true if shaders are compiled with debug information * @since 4.8 **/ bool isShaderDebug() const; /** * Binds the shader of specified @p type. * To unbind the shader use @link popShader. A previous bound shader will be rebound. * @param type The built-in shader to bind * @param reset Whether all uniforms should be reset to their default values * @return The bound shader or @c NULL if shaders are not valid * @see popShader **/ GLShader *pushShader(ShaderType type, bool reset = false); /** * Binds the @p shader. * To unbind the shader use @link popShader. A previous bound shader will be rebound. * To bind a built-in shader use the more specific method. * @param shader The shader to be bound * @see popShader **/ void pushShader(GLShader *shader); /** * Unbinds the currently bound shader and rebinds a previous stored shader. * If there is no previous shader, no shader will be rebound. * It is not safe to call this method if there is no bound shader. * @see pushShader * @see getBoundShader **/ void popShader(); /** * Creates a GLShader with a built-in vertex shader and a custom fragment shader. * @param vertex The generic vertex shader * @param fragmentFile The path to the source code of the fragment shader * @return The created shader **/ GLShader *loadFragmentShader(ShaderType vertex, const QString &fragmentFile); /** * Creates a GLShader with a built-in fragment shader and a custom vertex shader. * @param fragment The generic fragment shader * @param vertexFile The path to the source code of the vertex shader * @return The created shader **/ GLShader *loadVertexShader(ShaderType fragment, const QString &vertexFile); /** * Creates a GLShader with the specified sources. * The difference to GLShader is that it does not need to be loaded from files. * @param vertexSource The source code of the vertex shader * @param fragmentSource The source code of the fragment shader. * @return The created shader **/ GLShader *loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource); /** * @return a pointer to the ShaderManager instance **/ static ShaderManager *instance(); /** * @internal **/ static void cleanup(); private: ShaderManager(); ~ShaderManager(); void initShaders(); void resetShader(ShaderType type); QStack m_boundShaders; GLShader *m_orthoShader; GLShader *m_genericShader; GLShader *m_colorShader; bool m_inited; bool m_valid; bool m_debug; static ShaderManager *s_shaderManager; }; /** * @short Render target object * * Render target object enables you to render onto a texture. This texture can * later be used to e.g. do post-processing of the scene. * * @author Rivo Laks **/ class KWIN_EXPORT GLRenderTarget { public: /** * Constructs a GLRenderTarget * @param color texture where the scene will be rendered onto **/ - GLRenderTarget(GLTexture* color); + GLRenderTarget(const GLTexture& color); ~GLRenderTarget(); /** * Enables this render target. * All OpenGL commands from now on affect this render target until the * @ref disable method is called **/ bool enable(); /** * Disables this render target, activating whichever target was active * when @ref enable was called. **/ bool disable(); + /** + * Sets the target texture + * @param target texture where the scene will be rendered on + * @since 4.8 + **/ + void attachTexture(const GLTexture& target); + bool valid() const { return mValid; } static void initStatic(); static bool supported() { return sSupported; } static void pushRenderTarget(GLRenderTarget *target); static GLRenderTarget *popRenderTarget(); static bool isRenderTargetBound(); /** * Whether the GL_EXT_framebuffer_blit extension is supported. * This functionality is not available in OpenGL ES 2.0. * * @returns whether framebuffer blitting is supported. * @since 4.8 **/ static bool blitSupported(); /** * Blits the content of the current draw framebuffer into the texture attached to this FBO. * * Be aware that framebuffer blitting may not be supported on all hardware. Use @link blitSupported to check whether * it is supported. * @param source Geometry in screen coordinates which should be blitted, if not specified complete framebuffer is used * @param destination Geometry in attached texture, if not specified complete texture is used as destination * @param filter The filter to use if blitted content needs to be scaled. * @see blitSupported * @since 4.8 **/ void blitFromFramebuffer(const QRect &source = QRect(), const QRect &destination = QRect(), GLenum filter = GL_LINEAR); protected: void initFBO(); private: static bool sSupported; static bool s_blitSupported; static QStack s_renderTargets; + static QSize s_oldViewport; - GLTexture* mTexture; + GLTexture mTexture; bool mValid; GLuint mFramebuffer; }; /** * @short Vertex Buffer Object * * This is a short helper class to use vertex buffer objects (VBO). A VBO can be used to buffer * vertex data and to store them on graphics memory. It is the only allowed way to pass vertex * data to the GPU in OpenGL ES 2 and OpenGL 3 with forward compatible mode. * * If VBOs are not supported on the used OpenGL profile this class falls back to legacy * rendering using client arrays. Therefore this class should always be used for rendering geometries. * * @author Martin Gräßlin * @since 4.6 */ class KWIN_EXPORT GLVertexBuffer { public: /** * Enum to define how often the vertex data in the buffer object changes. */ enum UsageHint { Dynamic, ///< frequent changes, but used several times for rendering Static, ///< No changes to data Stream ///< Data only used once for rendering, updated very frequently }; GLVertexBuffer(UsageHint hint); ~GLVertexBuffer(); /** * Sets the vertex data. * @param numberVertices The number of vertices in the arrays * @param dim The dimension of the vertices: 2 for x/y, 3 for x/y/z * @param vertices The vertices, size must equal @a numberVertices * @a dim * @param texcoords The texture coordinates for each vertex. * Size must equal 2 * @a numberVertices. */ void setData(int numberVertices, int dim, const float* vertices, const float* texcoords); /** * Renders the vertex data in given @a primitiveMode. * Please refer to OpenGL documentation of glDrawArrays or glDrawElements for allowed * values for @a primitiveMode. Best is to use GL_TRIANGLES or similar to be future * compatible. */ void render(GLenum primitiveMode); /** * Same as above restricting painting to @a region. */ void render(const QRegion& region, GLenum primitiveMode); /** * Sets the color the geometry will be rendered with. * For legacy rendering glColor is used before rendering the geometry. * For core shader a uniform "geometryColor" is expected and is set. * @param color The color to render the geometry * @param enableColor Whether the geometry should be rendered with a color or not * @see setUseColor * @see isUseColor * @since 4.7 **/ void setColor(const QColor& color, bool enableColor = true); /** * @return @c true if geometry will be painted with a color, @c false otherwise * @see setUseColor * @see setColor * @since 4.7 **/ bool isUseColor() const; /** * Enables/Disables rendering the geometry with a color. * If no color is set an opaque, black color is used. * @param enable Enable/Disable rendering with color * @see isUseColor * @see setColor * @since 4.7 **/ void setUseColor(bool enable); /** * Resets the instance to default values. * Useful for shared buffers. * @since 4.7 **/ void reset(); /** * @internal */ static void initStatic(); /** * Returns true if VBOs are supported, it is save to use this class even if VBOs are not * supported. * @returns true if vertex buffer objects are supported */ static bool isSupported(); /** * @return A shared VBO for streaming data * @since 4.7 **/ static GLVertexBuffer *streamingBuffer(); private: GLVertexBufferPrivate* const d; }; } // namespace /** @} */ #endif