diff --git a/effects/blur/CMakeLists.txt b/effects/blur/CMakeLists.txt old mode 100644 new mode 100755 index e14819e4f..b4c99d32e --- a/effects/blur/CMakeLists.txt +++ b/effects/blur/CMakeLists.txt @@ -1,25 +1,24 @@ ####################################### # Config set(kwin_blur_config_SRCS blur_config.cpp) ki18n_wrap_ui(kwin_blur_config_SRCS blur_config.ui) qt5_add_dbus_interface(kwin_blur_config_SRCS ${kwin_effects_dbus_xml} kwineffects_interface) kconfig_add_kcfg_files(kwin_blur_config_SRCS blurconfig.kcfgc) add_library(kwin_blur_config MODULE ${kwin_blur_config_SRCS}) target_link_libraries(kwin_blur_config Qt5::DBus KF5::ConfigWidgets KF5::I18n KF5::Service - KF5::WindowSystem ) kcoreaddons_desktop_to_json(kwin_blur_config blur_config.desktop SERVICE_TYPES kcmodule.desktop) install( TARGETS kwin_blur_config DESTINATION ${PLUGIN_INSTALL_DIR}/kwin/effects/configs ) diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp old mode 100644 new mode 100755 index c353f6e96..b4af21535 --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -1,784 +1,769 @@ /* * Copyright © 2010 Fredrik Höglund * Copyright © 2011 Philipp Knechtges + * Copyright © 2018 Alex Nemeth * * 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 "effects.h" #include "blurshader.h" // KConfigSkeleton #include "blurconfig.h" #include #include +#include // for ceil() #include #include #include #include namespace KWin { static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); BlurEffect::BlurEffect() { initConfig(); - shader = BlurShader::create(); + m_shader = BlurShader::create(); m_simpleShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("logout-blur.frag")); + m_simpleTarget = new GLRenderTarget(); + if (!m_simpleShader->isValid()) { qCDebug(KWINEFFECTS) << "Simple blur shader failed to load"; } - updateTexture(); + initBlurStrengthValues(); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. - if (shader && shader->isValid() && target->valid()) { + if (m_shader && m_shader->isValid() && m_renderTargetsValid) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); KWayland::Server::Display *display = effects->waylandDisplay(); if (display) { m_blurManager = display->createBlurManager(this); m_blurManager->create(); } } else { net_wm_blur_region = 0; } connect(effects, SIGNAL(windowAdded(KWin::EffectWindow*)), this, SLOT(slotWindowAdded(KWin::EffectWindow*))); connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); connect(effects, SIGNAL(propertyNotify(KWin::EffectWindow*,long)), this, SLOT(slotPropertyNotify(KWin::EffectWindow*,long))); connect(effects, SIGNAL(screenGeometryChanged(QSize)), this, SLOT(slotScreenGeometryChanged())); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { - if (shader && shader->isValid() && target->valid()) { + if (m_shader && m_shader->isValid() && m_renderTargetsValid) { net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); } } ); // Fetch the blur regions for all windows foreach (EffectWindow *window, effects->stackingOrder()) updateBlurRegion(window); } BlurEffect::~BlurEffect() { - windows.clear(); + deleteFBOs(); + + delete m_simpleTarget; + m_simpleTarget = nullptr; delete m_simpleShader; - delete shader; - delete target; + m_simpleShader = nullptr; + + delete m_shader; + m_shader = nullptr; } void BlurEffect::slotScreenGeometryChanged() { effects->makeOpenGLContextCurrent(); updateTexture(); + // Fetch the blur regions for all windows foreach (EffectWindow *window, effects->stackingOrder()) updateBlurRegion(window); effects->doneOpenGLContextCurrent(); } -void BlurEffect::updateTexture() { - delete target; - // Offscreen texture that's used as the target for the horizontal blur pass - // and the source for the vertical pass. - tex = GLTexture(GL_RGBA8, effects->virtualScreenSize()); - tex.setFilter(GL_LINEAR); - tex.setWrapMode(GL_CLAMP_TO_EDGE); +bool BlurEffect::renderTargetsValid() const +{ + return !m_renderTargets.isEmpty() && std::find_if(m_renderTargets.cbegin(), m_renderTargets.cend(), + [](const GLRenderTarget *target) { + return !target->valid(); + }) == m_renderTargets.cend(); +} + +void BlurEffect::deleteFBOs() +{ + qDeleteAll(m_renderTargets); + + m_renderTargets.clear(); + m_renderTextures.clear(); +} + +void BlurEffect::updateTexture() +{ + deleteFBOs(); + + /* Reserve memory for: + * - The original sized texture (1) + * - The downsized textures (m_downSampleIterations) + * - The helper texture (1) + */ + m_renderTargets.reserve(m_downSampleIterations + 2); + m_renderTextures.reserve(m_downSampleIterations + 2); + + for (int i = 0; i <= m_downSampleIterations; i++) { + m_renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize() / (1 << i))); + m_renderTextures.last().setFilter(GL_LINEAR); + m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); + } + + // This last set is used as a temporary helper texture + m_renderTextures.append(GLTexture(GL_RGBA8, effects->virtualScreenSize())); + m_renderTextures.last().setFilter(GL_LINEAR); + m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); + + m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); + + m_renderTargetsValid = renderTargetsValid(); + + // Prepare the stack for the rendering + m_renderTargetStack.clear(); + m_renderTargets.reserve(m_downSampleIterations * 2 - 1); + + // Upsample + for (int i = 1; i < m_downSampleIterations; i++) { + m_renderTargetStack.push(m_renderTargets[i]); + } + + // Downsample + for (int i = m_downSampleIterations; i > 0; i--) { + m_renderTargetStack.push(m_renderTargets[i]); + } + + // Copysample + m_renderTargetStack.push(m_renderTargets[0]); +} + +void BlurEffect::initBlurStrengthValues() +{ + // This function creates an array of blur strength values that are evenly distributed + + // The range of the slider on the blur settings UI + int numOfBlurSteps = 15; + int remainingSteps = numOfBlurSteps; + + /* + * Explanation for these numbers: + * + * The texture blur amount depends on the downsampling iterations and the offset value. + * By changing the offset we can alter the blur amount without relying on further downsampling. + * But there is a minimum and maximum value of offset per downsample iteration before we + * get artifacts. + * + * The minOffset variable is the minimum offset value for an iteration before we + * get blocky artifacts because of the downsampling. + * + * The maxOffset value is the maximum offset value for an iteration before we + * get diagonal line artifacts because of the nature of the dual kawase blur algorithm. + * + * The expandSize value is the minimum value for an iteration before we reach the end + * of a texture in the shader and sample outside of the area that was copied into the + * texture from the screen. + */ + + // {minOffset, maxOffset, expandSize} + blurOffsets.append({1.0, 2.0, 10}); // Down sample size / 2 + blurOffsets.append({2.0, 3.0, 20}); // Down sample size / 4 + blurOffsets.append({2.0, 5.0, 50}); // Down sample size / 8 + blurOffsets.append({3.0, 8.0, 150}); // Down sample size / 16 + //blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32 + //blurOffsets.append({7.0, ?.0}); // Down sample size / 64 + + float offsetSum = 0; + + for (int i = 0; i < blurOffsets.size(); i++) { + offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset; + } + + for (int i = 0; i < blurOffsets.size(); i++) { + int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps); + remainingSteps -= iterationNumber; + + if (remainingSteps < 0) { + iterationNumber += remainingSteps; + } + + float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset; - target = new GLRenderTarget(tex); + for (int j = 1; j <= iterationNumber; j++) { + // {iteration, offset} + blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j}); + } + } } void BlurEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) BlurConfig::self()->read(); - int radius = qBound(2, BlurConfig::blurRadius(), 14); - if (shader) - shader->setRadius(radius); - m_shouldCache = effects->waylandDisplay() ? false : BlurConfig::cacheTexture(); + m_useSimpleBlur = BlurConfig::useSimpleBlur(); - windows.clear(); + int blurStrength = BlurConfig::blurStrength() - 1; + m_downSampleIterations = blurStrengthValues[blurStrength].iteration; + m_offset = blurStrengthValues[blurStrength].offset; + m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; - if (!shader || !shader->isValid()) { + updateTexture(); + + if (!m_shader || !m_shader->isValid()) { effects->removeSupportProperty(s_blurAtomName, this); delete m_blurManager; m_blurManager = nullptr; } + + // Update all windows for the blur to take effect + effects->addRepaintFull(); } void BlurEffect::updateBlurRegion(EffectWindow *w) const { QRegion region; QByteArray value; if (net_wm_blur_region != XCB_ATOM_NONE) { value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { const uint32_t *cardinals = reinterpret_cast(value.constData()); for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; region += QRect(x, y, w, h); } } } KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf && surf->blur()) { region = surf->blur()->region(); } //!value.isNull() full window in X11 case, surf->blur() //valid, full window in wayland case if (region.isEmpty() && (!value.isNull() || (surf && surf->blur()))) { // 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) { KWayland::Server::SurfaceInterface *surf = w->surface(); if (surf) { - windows[w].blurChangedConnection = connect(surf, &KWayland::Server::SurfaceInterface::blurChanged, this, [this, w] () { - + windowBlurChangedConnections[w] = connect(surf, &KWayland::Server::SurfaceInterface::blurChanged, this, [this, w] () { if (w) { updateBlurRegion(w); } }); } + updateBlurRegion(w); } void BlurEffect::slotWindowDeleted(EffectWindow *w) { - if (windows.contains(w)) { - disconnect(windows[w].blurChangedConnection); - windows.remove(w); + if (windowBlurChangedConnections.contains(w)) { + disconnect(windowBlurChangedConnections[w]); + windowBlurChangedConnections.remove(w); } } void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) { updateBlurRegion(w); - CacheEntry it = windows.find(w); - if (it != windows.end()) { - const QRect screen = effects->virtualScreenGeometry(); - it->damagedRegion = expand(blurRegion(w).translated(w->pos())) & screen; - } } } bool BlurEffect::enabledByDefault() { GLPlatform *gl = GLPlatform::instance(); if (gl->isIntel() && gl->chipClass() < SandyBridge) return false; if (gl->isSoftwareEmulation()) { return false; } return true; } bool BlurEffect::supported() { bool supported = effects->isOpenGLCompositing() && GLRenderTarget::supported(); if (supported) { int maxTexSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); const QSize screenSize = effects->virtualScreenSize(); if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) supported = false; } return supported; } QRect BlurEffect::expand(const QRect &rect) const { - const int radius = shader->radius(); - return rect.adjusted(-radius, -radius, radius, radius); + return rect.adjusted(-m_expandSize, -m_expandSize, m_expandSize, m_expandSize); } QRegion BlurEffect::expand(const QRegion ®ion) const { QRegion expanded; for (const QRect &rect : region) { 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->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) { region = w->shape(); region -= w->decorationInnerRect(); } region |= appRegion.translated(w->contentsRect().topLeft()) & w->decorationInnerRect(); } else { // An empty region means that the blur effect should be enabled // for the whole window. region = w->shape(); } } else if (w->decorationHasAlpha() && 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::uploadRegion(QVector2D *&map, const QRegion ®ion) +void BlurEffect::uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations) { - for (const QRect &r : region) { - const QVector2D topLeft(r.x(), r.y()); - const QVector2D topRight(r.x() + r.width(), r.y()); - const QVector2D bottomLeft(r.x(), r.y() + r.height()); - const QVector2D bottomRight(r.x() + r.width(), r.y() + r.height()); - - // First triangle - *(map++) = topRight; - *(map++) = topLeft; - *(map++) = bottomLeft; - - // Second triangle - *(map++) = bottomLeft; - *(map++) = bottomRight; - *(map++) = topRight; + for (int i = 0; i <= downSampleIterations; i++) { + const int divisionRatio = (1 << i); + + for (const QRect &r : region) { + const QVector2D topLeft( r.x() / divisionRatio, r.y() / divisionRatio); + const QVector2D topRight( (r.x() + r.width()) / divisionRatio, r.y() / divisionRatio); + const QVector2D bottomLeft( r.x() / divisionRatio, (r.y() + r.height()) / divisionRatio); + const QVector2D bottomRight((r.x() + r.width()) / divisionRatio, (r.y() + r.height()) / divisionRatio); + + // First triangle + *(map++) = topRight; + *(map++) = topLeft; + *(map++) = bottomLeft; + + // Second triangle + *(map++) = bottomLeft; + *(map++) = bottomRight; + *(map++) = topRight; + } } } -void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical) +void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &windowRegion) { - const int vertexCount = (horizontal.rectCount() + vertical.rectCount()) * 6; + const int vertexCount = ((blurRegion.rectCount() * (m_downSampleIterations + 1)) + windowRegion.rectCount()) * 6; + if (!vertexCount) return; QVector2D *map = (QVector2D *) vbo->map(vertexCount * sizeof(QVector2D)); - uploadRegion(map, horizontal); - uploadRegion(map, vertical); + + uploadRegion(map, blurRegion, m_downSampleIterations); + uploadRegion(map, windowRegion, 0); + vbo->unmap(); const GLVertexAttrib layout[] = { { VA_Position, 2, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, 0 } }; vbo->setAttribLayout(layout, 2, sizeof(QVector2D)); } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_damagedArea = QRegion(); m_paintedArea = QRegion(); m_currentBlur = QRegion(); 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; } - if (!shader || !shader->isValid()) { + if (!m_shader || !m_shader->isValid()) { 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(); for (const QRect &rect : data.clip) { - newClip |= rect.adjusted(radius,radius,-radius,-radius); + newClip |= rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize); } data.clip = newClip; const QRegion oldPaint = data.paint; // 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 (which is not cached) we have to redraw the whole region - if ((data.paint-oldClip).intersects(m_currentBlur)) { + // currently blurred region we have to redraw the whole region + if ((data.paint - oldClip).intersects(m_currentBlur)) { data.paint |= m_currentBlur; } // in case this window has regions to be blurred const QRect screen = effects->virtualScreenGeometry(); const QRegion blurArea = blurRegion(w).translated(w->pos()) & screen; - const QRegion expandedBlur = expand(blurArea) & screen; - - if (m_shouldCache && !w->isDeleted()) { - // we are caching the horizontally blurred background texture - - // if a window underneath the blurred area is damaged we have to - // update the cached texture - QRegion damagedCache; - CacheEntry it = windows.find(w); - if (it != windows.end() && !it->dropCache && - it->windowPos == w->pos() && - it->blurredBackground.size() == expandedBlur.boundingRect().size()) { - damagedCache = (expand(expandedBlur & m_damagedArea) | - (it->damagedRegion & data.paint)) & expandedBlur; - } else { - damagedCache = expandedBlur; - } - if (!damagedCache.isEmpty()) { - // This is the area of the blurry window which really can change. - const QRegion damagedArea = damagedCache & blurArea; - // In order to be able to recalculate this area we have to make sure the - // background area is painted before. - data.paint |= expand(damagedArea); - if (it != windows.end()) { - // In case we already have a texture cache mark the dirty regions invalid. - it->damagedRegion &= expandedBlur; - it->damagedRegion |= damagedCache; - // The valid part of the cache can be considered as being opaque - // as long as we don't need to update a bordering part - data.clip |= blurArea - expand(it->damagedRegion); - it->dropCache = false; - } - // we keep track of the "damage propagation" - m_damagedArea |= 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; - } - } - } 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(expandedBlur) || data.paint.intersects(blurArea)) { - data.paint |= expandedBlur; - // we keep track of the "damage propagation" - m_damagedArea |= expand(expandedBlur & m_damagedArea) & blurArea; - // we have to check again whether we do not damage a blurred area - // of a window we do not cache - if (expandedBlur.intersects(m_currentBlur)) { - data.paint |= m_currentBlur; - } + const QRegion expandedBlur = (w->isDock() ? blurArea : expand(blurArea)) & screen; + + // if this window or a window underneath the blurred area is painted again we have to + // blur everything + if (m_paintedArea.intersects(expandedBlur) || data.paint.intersects(blurArea)) { + data.paint |= expandedBlur; + // we keep track of the "damage propagation" + m_damagedArea |= (w->isDock() ? (expandedBlur & m_damagedArea) : expand(expandedBlur & m_damagedArea)) & blurArea; + // we have to check again whether we do not damage a blurred area + // of a window + 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 |= 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 || !shader->isValid()) + if (!m_renderTargetsValid || !m_shader || !m_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.xTranslation() || data.yTranslation(); if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) return false; bool blurBehindDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind(); if (!w->hasAlpha() && w->opacity() >= 1.0 && !(blurBehindDecos && w->hasDecoration())) return false; return true; } void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { const QRect screen = GLRenderTarget::virtualScreenGeometry(); if (shouldBlur(w, mask, data)) { QRegion shape = region & blurRegion(w).translated(w->pos()) & screen; // let's do the evil parts - someone wants to blur behind a transformed window const bool translated = data.xTranslation() || data.yTranslation(); const bool scaled = data.xScale() != 1 || data.yScale() != 1; if (scaled) { QPoint pt = shape.boundingRect().topLeft(); QVector shapeRects = shape.rects(); shape = QRegion(); // clear foreach (QRect r, shapeRects) { r.moveTo(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); shape |= r; } shape = shape & region; //Only translated, not scaled } else if (translated) { shape = shape.translated(data.xTranslation(), data.yTranslation()); shape = shape & region; } if (!shape.isEmpty()) { - if (w->isFullScreen() && GLRenderTarget::blitSupported() && m_simpleShader->isValid() - && !GLPlatform::instance()->supports(LimitedNPOT) && shape.boundingRect() == w->geometry()) { - doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); - } else if (m_shouldCache && !translated && !w->isDeleted()) { - doCachedBlur(w, region, data.opacity(), data.screenProjectionMatrix()); + if (m_useSimpleBlur && + w->isFullScreen() && + GLRenderTarget::blitSupported() && + m_simpleShader->isValid() && + !GLPlatform::instance()->supports(LimitedNPOT) && + shape.boundingRect() == w->geometry()) { + doSimpleBlur(w, data.opacity(), data.screenProjectionMatrix()); } else { - doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix()); + doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix(), w->isDock()); } } } // 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 = effects->virtualScreenGeometry(); - bool valid = target->valid() && shader && shader->isValid(); - QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen; + bool valid = m_renderTargetsValid && m_shader && m_shader->isValid(); + + QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; + if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { - doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix()); + doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false); } effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void BlurEffect::doSimpleBlur(EffectWindow *w, const float opacity, const QMatrix4x4 &screenProjection) { // The fragment shader uses a LOD bias of 1.75, so we need 3 mipmap levels. GLTexture blurTexture = GLTexture(GL_RGBA8, w->size(), 3); blurTexture.setFilter(GL_LINEAR_MIPMAP_LINEAR); blurTexture.setWrapMode(GL_CLAMP_TO_EDGE); - target->attachTexture(blurTexture); - target->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); + m_simpleTarget->attachTexture(blurTexture); + m_simpleTarget->blitFromFramebuffer(w->geometry(), QRect(QPoint(0, 0), w->size())); + m_simpleTarget->detachTexture(); // Unmodified base image ShaderBinder binder(m_simpleShader); QMatrix4x4 mvp = screenProjection; mvp.translate(w->x(), w->y()); m_simpleShader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_simpleShader->setUniform("u_alphaProgress", opacity); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); blurTexture.bind(); blurTexture.generateMipmaps(); blurTexture.render(infiniteRegion(), w->geometry()); blurTexture.unbind(); glDisable(GL_BLEND); } -void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection) +void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock) { - const QRegion expanded = expand(shape) & screen; - const QRect r = expanded.boundingRect(); + QRegion expandedBlurRegion = expand(shape) & expand(screen); - // Upload geometry for the horizontal and vertical passes + // Upload geometry for the down and upsample iterations GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, expanded, shape); + uploadGeometry(vbo, expandedBlurRegion, shape); vbo->bindArrays(); - const qreal scale = GLRenderTarget::virtualScreenScale(); - - // Create a scratch texture and copy the area in the back buffer that we're - // going to blur into it - // for HIGH DPI scratch is captured in native resolution, it is then implicitly downsampled - // when rendering into tex - GLTexture scratch(GL_RGBA8, r.width() * scale, r.height() * scale); - scratch.setFilter(GL_LINEAR); - scratch.setWrapMode(GL_CLAMP_TO_EDGE); - scratch.bind(); - - const QRect sg = GLRenderTarget::virtualScreenGeometry(); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (r.x() - sg.x()) * scale, (sg.height() - sg.y() - r.y() - r.height()) * scale, - scratch.width(), scratch.height()); - - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(tex); - GLRenderTarget::pushRenderTarget(target); - - shader->bind(); - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / r.width()); - - QMatrix4x4 modelViewProjectionMatrix; - modelViewProjectionMatrix.ortho(0, tex.width(), tex.height(), 0 , 0, 65535); - shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); - - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - QMatrix4x4 textureMatrix; - textureMatrix.scale(1.0 / r.width(), -1.0 / r.height(), 1); - textureMatrix.translate(-r.x(), (-r.height() - r.y()), 0); - shader->setTextureMatrix(textureMatrix); - - vbo->draw(GL_TRIANGLES, 0, expanded.rectCount() * 6); + /* + * If the window is a dock or panel we avoid the "extended blur" effect. + * Extended blur is when windows that are not under the blurred area affect + * the final blur result. + * We want to avoid this on panels, because it looks really weird and ugly + * when maximized windows or windows near the panel affect the dock blur. + */ + isDock ? m_renderTextures.last().bind() : m_renderTextures[0].bind(); + + QRect copyRect = expandedBlurRegion.boundingRect() & screen; + glCopyTexSubImage2D( + GL_TEXTURE_2D, + 0, + copyRect.x(), + effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), + copyRect.x(), + effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), + copyRect.width(), + copyRect.height() + ); - GLRenderTarget::popRenderTarget(); - scratch.unbind(); - scratch.discard(); + GLRenderTarget::pushRenderTargets(m_renderTargetStack); + int blurRectCount = expandedBlurRegion.rectCount() * 6; - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - tex.bind(); + if (isDock) { + copyScreenSampleTexture(vbo, blurRectCount, shape, screen.size(), screenProjection); + } else { + // Remove the m_renderTargets[0] from the top of the stack that we will not use + GLRenderTarget::popRenderTarget(); + } - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / tex.height()); + downSampleTexture(vbo, blurRectCount); + upSampleTexture(vbo, blurRectCount); // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { glEnable(GL_BLEND); #if 1 // bow shape, always above y = x float o = 1.0f-opacity; o = 1.0f - o*o; #else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5 float o = 2.0f*opacity - 1.0f; o = 0.5f + o / (1.0f + qAbs(o)); #endif glBlendColor(0, 0, 0, o); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(0, -tex.height(), 0); - shader->setTextureMatrix(textureMatrix); - shader->setModelViewProjectionMatrix(screenProjection); + //Final upscale to the screen + m_shader->bind(BlurShader::UpSampleType); + m_shader->setOffset(m_offset); - vbo->draw(GL_TRIANGLES, expanded.rectCount() * 6, shape.rectCount() * 6); - vbo->unbindArrays(); + m_shader->setModelViewProjectionMatrix(screenProjection); + m_shader->setTargetSize(m_renderTextures[0].size()); + + //Copy the image from this texture + m_renderTextures[1].bind(); + + //Render to the screen + vbo->draw(GL_TRIANGLES, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); if (opacity < 1.0) { glDisable(GL_BLEND); } - tex.unbind(); - shader->unbind(); + vbo->unbindArrays(); + m_shader->unbind(); } -void BlurEffect::doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection) +void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) { - const QRect screen = effects->virtualScreenGeometry(); - const QRegion blurredRegion = blurRegion(w).translated(w->pos()) & screen; - const QRegion expanded = expand(blurredRegion) & screen; - const QRect r = expanded.boundingRect(); - - // The background texture we get is only partially valid. - - CacheEntry it = windows.find(w); - if (it == windows.end()) { - BlurWindowInfo bwi; - bwi.blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); - bwi.damagedRegion = expanded; - bwi.dropCache = false; - bwi.windowPos = w->pos(); - it = windows.insert(w, bwi); - } else if (it->blurredBackground.size() != r.size()) { - it->blurredBackground = GLTexture(GL_RGBA8, r.width(),r.height()); - it->dropCache = false; - it->windowPos = w->pos(); - } else if (it->windowPos != w->pos()) { - it->dropCache = false; - it->windowPos = w->pos(); - } - - GLTexture targetTexture = it->blurredBackground; - targetTexture.setFilter(GL_LINEAR); - targetTexture.setWrapMode(GL_CLAMP_TO_EDGE); - shader->bind(); - QMatrix4x4 textureMatrix; QMatrix4x4 modelViewProjectionMatrix; - /** - * Which part of the background texture can be updated ? - * - * Well this is a rather difficult question. We kind of rely on the fact, that - * we need a bigger background region being painted before, more precisely if we want to - * blur region A we need the background region expand(A). This business logic is basically - * done in prePaintWindow: - * data.paint |= expand(damagedArea); - * - * Now "data.paint" gets clipped and becomes what we receive as the "region" variable - * in this function. In theory there is now only one function that does this clipping - * and this is paintSimpleScreen. The clipping has the effect that "damagedRegion" - * is no longer a subset of "region" and we cannot fully validate the cache within one - * rendering pass. If we would now update the "damageRegion & region" part of the cache - * we would wrongly update the part of the cache that is next to the "region" border and - * which lies within "damagedRegion", just because we cannot assume that the framebuffer - * outside of "region" is valid. Therefore the maximal damaged region of the cache that can - * be repainted is given by: - * validUpdate = damagedRegion - expand(damagedRegion - region); - * - * Now you may ask what is with the rest of "damagedRegion & region" that is not part - * of "validUpdate" but also might end up on the screen. Well under the assumption - * that only the occlusion culling can shrink "data.paint", we can control this by reducing - * the opaque area of every window by a margin of the blurring radius (c.f. prePaintWindow). - * This way we are sure that this area is overpainted by a higher opaque window. - * - * Apparently paintSimpleScreen is not the only function that can influence "region". - * In fact every effect's paintWindow that is called before Blur::paintWindow - * can do so (e.g. SlidingPopups). Hence we have to make the compromise that we update - * "damagedRegion & region" of the cache but only mark "validUpdate" as valid. - **/ - const QRegion damagedRegion = it->damagedRegion; - const QRegion updateBackground = damagedRegion & region; - const QRegion validUpdate = damagedRegion - expand(damagedRegion - region); + m_shader->bind(BlurShader::DownSampleType); + m_shader->setOffset(m_offset); - const QRegion horizontal = validUpdate.isEmpty() ? QRegion() : (updateBackground & screen); - const QRegion vertical = blurredRegion & region; + for (int i = 1; i <= m_downSampleIterations; i++) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); - const int horizontalOffset = 0; - const int horizontalCount = horizontal.rectCount() * 6; + m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + m_shader->setTargetSize(m_renderTextures[i].size()); - const int verticalOffset = horizontalCount; - const int verticalCount = vertical.rectCount() * 6; + //Copy the image from this texture + m_renderTextures[i - 1].bind(); - GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); - uploadGeometry(vbo, horizontal, vertical); - - vbo->bindArrays(); - - if (!validUpdate.isEmpty()) { - const QRect updateRect = (expand(updateBackground) & expanded).boundingRect(); - // First we have to copy the background from the frontbuffer - // into a scratch texture (in this case "tex"). - tex.bind(); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); + GLRenderTarget::popRenderTarget(); + } - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, updateRect.x(), effects->virtualScreenSize().height() - updateRect.y() - updateRect.height(), - updateRect.width(), updateRect.height()); + m_shader->unbind(); +} - // Draw the texture on the offscreen framebuffer object, while blurring it horizontally - target->attachTexture(targetTexture); - GLRenderTarget::pushRenderTarget(target); +void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) +{ + QMatrix4x4 modelViewProjectionMatrix; - shader->setDirection(Qt::Horizontal); - shader->setPixelDistance(1.0 / tex.width()); + m_shader->bind(BlurShader::UpSampleType); + m_shader->setOffset(m_offset); - modelViewProjectionMatrix.ortho(0, r.width(), r.height(), 0 , 0, 65535); - modelViewProjectionMatrix.translate(-r.x(), -r.y(), 0); - shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + for (int i = m_downSampleIterations - 1; i > 0; i--) { + modelViewProjectionMatrix.setToIdentity(); + modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); - // Set up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); - textureMatrix.translate(-updateRect.x(), -updateRect.height() - updateRect.y(), 0); - shader->setTextureMatrix(textureMatrix); + m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); + m_shader->setTargetSize(m_renderTextures[i].size()); - vbo->draw(GL_TRIANGLES, horizontalOffset, horizontalCount); + //Copy the image from this texture + m_renderTextures[i + 1].bind(); + vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); GLRenderTarget::popRenderTarget(); - tex.unbind(); - // mark the updated region as valid - it->damagedRegion -= validUpdate; } - // Now draw the horizontally blurred area back to the backbuffer, while - // blurring it vertically and clipping it to the window shape. - targetTexture.bind(); - - shader->setDirection(Qt::Vertical); - shader->setPixelDistance(1.0 / targetTexture.height()); - - // Modulate the blurred texture with the window opacity if the window isn't opaque - if (opacity < 1.0) { - glEnable(GL_BLEND); - glBlendColor(0, 0, 0, opacity); - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - } + m_shader->unbind(); +} - shader->setModelViewProjectionMatrix(screenProjection); +void BlurEffect::copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection) +{ + m_shader->bind(BlurShader::CopySampleType); - // Set the up the texture matrix to transform from screen coordinates - // to texture coordinates. - textureMatrix.setToIdentity(); - textureMatrix.scale(1.0 / targetTexture.width(), -1.0 / targetTexture.height(), 1); - textureMatrix.translate(-r.x(), -targetTexture.height() - r.y(), 0); - shader->setTextureMatrix(textureMatrix); + m_shader->setModelViewProjectionMatrix(screenProjection); + m_shader->setTargetSize(screenSize); - vbo->draw(GL_TRIANGLES, verticalOffset, verticalCount); - vbo->unbindArrays(); + /* + * This '1' sized adjustment is necessary do avoid windows affecting the blur that are + * right next to this window. + */ + m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); - if (opacity < 1.0) { - glDisable(GL_BLEND); - } - - targetTexture.unbind(); - shader->unbind(); -} + vbo->draw(GL_TRIANGLES, 0, blurRectCount); + GLRenderTarget::popRenderTarget(); -int BlurEffect::blurRadius() const -{ - if (!shader) { - return 0; - } - return shader->radius(); + m_shader->unbind(); } } // namespace KWin diff --git a/effects/blur/blur.h b/effects/blur/blur.h old mode 100644 new mode 100755 index 85a7b4ae6..2c865187f --- a/effects/blur/blur.h +++ b/effects/blur/blur.h @@ -1,128 +1,146 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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 +#include namespace KWayland { namespace Server { class BlurManagerInterface; } } namespace KWin { +static const int borderSize = 5; + class BlurShader; class BlurEffect : public KWin::Effect { Q_OBJECT - Q_PROPERTY(int blurRadius READ blurRadius) - Q_PROPERTY(bool cacheTexture READ isCacheTexture) + public: BlurEffect(); ~BlurEffect(); static bool supported(); static bool enabledByDefault(); void reconfigure(ReconfigureFlags flags); void prePaintScreen(ScreenPrePaintData &data, int time); void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); void drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); void paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity); - // for dynamic setting extraction - int blurRadius() const; - bool isCacheTexture() const { - return m_shouldCache; - } virtual bool provides(Feature feature); int requestedEffectChainPosition() const override { return 75; } public Q_SLOTS: void slotWindowAdded(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); void slotPropertyNotify(KWin::EffectWindow *w, long atom); void slotScreenGeometryChanged(); private: - void updateTexture(); QRect expand(const QRect &rect) const; QRegion expand(const QRegion ®ion) const; + bool renderTargetsValid() const; + void deleteFBOs(); + void initBlurStrengthValues(); + void updateTexture(); QRegion blurRegion(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; void updateBlurRegion(EffectWindow *w) const; void doSimpleBlur(EffectWindow *w, const float opacity, const QMatrix4x4 &screenProjection); - void doBlur(const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection); - void doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity, const QMatrix4x4 &screenProjection); - void uploadRegion(QVector2D *&map, const QRegion ®ion); - void uploadGeometry(GLVertexBuffer *vbo, const QRegion &horizontal, const QRegion &vertical); + void doBlur(const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock); + void uploadRegion(QVector2D *&map, const QRegion ®ion, const int downSampleIterations); + void uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &windowRegion); + + void downSampleTexture(GLVertexBuffer *vbo, int blurRectCount); + void upSampleTexture(GLVertexBuffer *vbo, int blurRectCount); + void copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection); private: - BlurShader *shader; GLShader *m_simpleShader; - GLRenderTarget *target = nullptr; - GLTexture tex; + GLRenderTarget *m_simpleTarget; + + BlurShader *m_shader; + QVector m_renderTargets; + QVector m_renderTextures; + QStack m_renderTargetStack; + bool m_renderTargetsValid; long net_wm_blur_region; QRegion m_damagedArea; // keeps track of the area which has been damaged (from bottom to top) QRegion m_paintedArea; // actually painted area which is greater than m_damagedArea - QRegion m_currentBlur; // keeps track of the currently blured area of non-caching windows(from bottom to top) - bool m_shouldCache; - - struct BlurWindowInfo { - GLTexture blurredBackground; // keeps the horizontally blurred background - QRegion damagedRegion; - QPoint windowPos; - bool dropCache; - QMetaObject::Connection blurChangedConnection; + QRegion m_currentBlur; // keeps track of the currently blured area of the windows(from bottom to top) + bool m_useSimpleBlur; + + int m_downSampleIterations; // number of times the texture will be downsized to half size + int m_offset; + int m_expandSize; + + struct OffsetStruct { + float minOffset; + float maxOffset; + int expandSize; + }; + + QVector blurOffsets; + + struct BlurValuesStruct { + int iteration; + float offset; }; - QHash< const EffectWindow*, BlurWindowInfo > windows; - typedef QHash::iterator CacheEntry; + QVector blurStrengthValues; + + QMap windowBlurChangedConnections; KWayland::Server::BlurManagerInterface *m_blurManager = nullptr; }; inline bool BlurEffect::provides(Effect::Feature feature) { if (feature == Blur) { return true; } return KWin::Effect::provides(feature); } } // namespace KWin #endif diff --git a/effects/blur/blur.kcfg b/effects/blur/blur.kcfg old mode 100644 new mode 100755 index 6fd2e98c4..9fc659d3a --- a/effects/blur/blur.kcfg +++ b/effects/blur/blur.kcfg @@ -1,15 +1,15 @@ - - 12 + + 5 - - true + + false diff --git a/effects/blur/blur_config.cpp b/effects/blur/blur_config.cpp old mode 100644 new mode 100755 index b0aacadec..a39882a72 --- a/effects/blur/blur_config.cpp +++ b/effects/blur/blur_config.cpp @@ -1,66 +1,62 @@ /* * 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" // KConfigSkeleton #include "blurconfig.h" #include #include #include #include -#include K_PLUGIN_FACTORY_WITH_JSON(BlurEffectConfigFactory, "blur_config.json", registerPlugin();) namespace KWin { BlurEffectConfig::BlurEffectConfig(QWidget *parent, const QVariantList &args) : KCModule(KAboutData::pluginData(QStringLiteral("blur")), parent, args) { ui.setupUi(this); - if (KWindowSystem::isPlatformWayland()) { - ui.kcfg_CacheTexture->setVisible(false); - } BlurConfig::instance(KWIN_CONFIG); addConfig(BlurConfig::self(), this); load(); } BlurEffectConfig::~BlurEffectConfig() { } void BlurEffectConfig::save() { KCModule::save(); OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QDBusConnection::sessionBus()); interface.reconfigureEffect(QStringLiteral("blur")); } } // namespace KWin #include "blur_config.moc" diff --git a/effects/blur/blur_config.desktop b/effects/blur/blur_config.desktop old mode 100644 new mode 100755 diff --git a/effects/blur/blur_config.h b/effects/blur/blur_config.h old mode 100644 new mode 100755 diff --git a/effects/blur/blur_config.ui b/effects/blur/blur_config.ui old mode 100644 new mode 100755 index 933d0d0a8..068637ca0 --- a/effects/blur/blur_config.ui +++ b/effects/blur/blur_config.ui @@ -1,107 +1,107 @@ BlurEffectConfig 0 0 - 396 - 103 + 480 + 95 - + Strength of the effect: Qt::Horizontal QSizePolicy::Fixed 20 20 - + Light - + 1 - 14 + 15 - 2 + 1 - 2 + 1 - 12 + 5 Qt::Horizontal QSlider::TicksBelow - + Strong - - - - + - Save intermediate rendering results. + Use simple fullscreen blur + + + false Qt::Vertical 0 0 diff --git a/effects/blur/blurconfig.kcfgc b/effects/blur/blurconfig.kcfgc old mode 100644 new mode 100755 diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp old mode 100644 new mode 100755 index 579e5cd3b..c6957a0d9 --- a/effects/blur/blurshader.cpp +++ b/effects/blur/blurshader.cpp @@ -1,306 +1,384 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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 "kwinglutils.h" #include #include #include #include #include #include using namespace KWin; BlurShader::BlurShader() - : mRadius(0), mValid(false) + : mValid(false) { } BlurShader::~BlurShader() { } BlurShader *BlurShader::create() { return new GLSLBlurShader(); } -void BlurShader::setRadius(int radius) -{ - const int r = qMax(radius, 2); +// ---------------------------------------------------------------------------- + - if (mRadius != r) { - mRadius = r; - reset(); - init(); - } -} -void BlurShader::setDirection(Qt::Orientation direction) +GLSLBlurShader::GLSLBlurShader() { - mDirection = direction; + init(); } -float BlurShader::gaussian(float x, float sigma) const +GLSLBlurShader::~GLSLBlurShader() { - return (1.0 / std::sqrt(2.0 * M_PI) * sigma) - * std::exp(-((x * x) / (2.0 * sigma * sigma))); + reset(); } -QList BlurShader::gaussianKernel() const +void GLSLBlurShader::reset() { - int size = qMin(mRadius | 1, maxKernelSize()); - if (!(size & 0x1)) - size -= 1; + delete m_shaderDownsample; + m_shaderDownsample = nullptr; - QList kernel; - const int center = size / 2; - const qreal sigma = (size - 1) / 2.5; + delete m_shaderUpsample; + m_shaderUpsample = nullptr; - kernel << KernelValue(0.0, gaussian(0.0, sigma)); - float total = kernel[0].g; + delete m_shaderCopysample; + m_shaderCopysample = nullptr; + + setIsValid(false); +} - for (int x = 1; x <= center; x++) { - const float fx = (x - 1) * 2 + 1.5; - const float g1 = gaussian(fx - 0.5, sigma); - const float g2 = gaussian(fx + 0.5, sigma); +void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +{ + if (!isValid()) + return; - // Offset taking the contribution of both pixels into account - const float offset = .5 - g1 / (g1 + g2); + switch (m_activeSampleType) { + case CopySampleType: + if (matrix == m_matrixCopysample) + return; - kernel << KernelValue(fx + offset, g1 + g2); - kernel << KernelValue(-(fx + offset), g1 + g2); + m_matrixCopysample = matrix; + m_shaderCopysample->setUniform(m_mvpMatrixLocationCopysample, matrix); + break; - total += (g1 + g2) * 2; - } + case UpSampleType: + if (matrix == m_matrixUpsample) + return; - qSort(kernel); + m_matrixUpsample = matrix; + m_shaderUpsample->setUniform(m_mvpMatrixLocationUpsample, matrix); + break; - // Normalize the kernel - for (int i = 0; i < kernel.count(); i++) - kernel[i].g /= total; + case DownSampleType: + if (matrix == m_matrixDownsample) + return; - return kernel; + m_matrixDownsample = matrix; + m_shaderDownsample->setUniform(m_mvpMatrixLocationDownsample, matrix); + break; + } } +void GLSLBlurShader::setOffset(float offset) +{ + if (!isValid()) + return; + switch (m_activeSampleType) { + case UpSampleType: + if (offset == m_offsetUpsample) + return; -// ---------------------------------------------------------------------------- - + m_offsetUpsample = offset; + m_shaderUpsample->setUniform(m_offsetLocationUpsample, offset); + break; + case DownSampleType: + if (offset == m_offsetDownsample) + return; -GLSLBlurShader::GLSLBlurShader() - : BlurShader(), shader(NULL) -{ + m_offsetDownsample = offset; + m_shaderDownsample->setUniform(m_offsetLocationDownsample, offset); + break; + } } -GLSLBlurShader::~GLSLBlurShader() +void GLSLBlurShader::setTargetSize(QSize renderTextureSize) { - reset(); -} + if (!isValid()) + return; -void GLSLBlurShader::reset() -{ - delete shader; - shader = NULL; + QVector2D texSize = QVector2D(renderTextureSize.width(), renderTextureSize.height()); - setIsValid(false); -} + switch (m_activeSampleType) { + case CopySampleType: + if (renderTextureSize == m_renderTextureSizeCopysample) + return; -void GLSLBlurShader::setPixelDistance(float val) -{ - if (!isValid()) - return; + m_renderTextureSizeCopysample = renderTextureSize; + m_shaderCopysample->setUniform(m_renderTextureSizeLocationCopysample, texSize); + break; - QVector2D pixelSize(0.0, 0.0); - if (direction() == Qt::Horizontal) - pixelSize.setX(val); - else - pixelSize.setY(val); + case UpSampleType: + if (renderTextureSize == m_renderTextureSizeUpsample) + return; - shader->setUniform(pixelSizeLocation, pixelSize); -} + m_renderTextureSizeUpsample = renderTextureSize; + m_shaderUpsample->setUniform(m_renderTextureSizeLocationUpsample, texSize); + m_shaderUpsample->setUniform(m_halfpixelLocationUpsample, QVector2D(0.5 / texSize.x(), 0.5 / texSize.y())); + break; -void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix) -{ - if (!isValid()) - return; + case DownSampleType: + if (renderTextureSize == m_renderTextureSizeDownsample) + return; - shader->setUniform(textureMatrixLocation, matrix); + m_renderTextureSizeDownsample = renderTextureSize; + m_shaderDownsample->setUniform(m_renderTextureSizeLocationDownsample, texSize); + m_shaderDownsample->setUniform(m_halfpixelLocationDownsample, QVector2D(0.5 / texSize.x(), 0.5 / texSize.y())); + break; + } } -void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) +void GLSLBlurShader::setBlurRect(QRect blurRect, QSize screenSize) { - if (!isValid()) + if (!isValid() || blurRect == m_blurRectCopysample) return; - shader->setUniform(mvpMatrixLocation, matrix); + m_blurRectCopysample = blurRect; + + QVector4D rect = QVector4D( + blurRect.bottomLeft().x() / float(screenSize.width()), + 1.0 - blurRect.bottomLeft().y() / float(screenSize.height()), + blurRect.topRight().x() / float(screenSize.width()), + 1.0 - blurRect.topRight().y() / float(screenSize.height()) + ); + + m_shaderCopysample->setUniform(m_blurRectLocationCopysample, rect); } -void GLSLBlurShader::bind() +void GLSLBlurShader::bind(SampleType sampleType) { if (!isValid()) return; - ShaderManager::instance()->pushShader(shader); + switch (sampleType) { + case CopySampleType: + ShaderManager::instance()->pushShader(m_shaderCopysample); + break; + + case UpSampleType: + ShaderManager::instance()->pushShader(m_shaderUpsample); + break; + + case DownSampleType: + ShaderManager::instance()->pushShader(m_shaderDownsample); + break; + } + + m_activeSampleType = sampleType; } void GLSLBlurShader::unbind() { ShaderManager::instance()->popShader(); } -int GLSLBlurShader::maxKernelSize() const -{ - if (GLPlatform::instance()->isGLES()) { - // GL_MAX_VARYING_FLOATS not available in GLES - // querying for GL_MAX_VARYING_VECTORS crashes on nouveau - // using the minimum value of 8 - return 8 * 2; - } else { - int value; - glGetIntegerv(GL_MAX_VARYING_FLOATS, &value); - // Maximum number of vec4 varyings * 2 - // The code generator will pack two vec2's into each vec4. - return value / 2; - } -} - void GLSLBlurShader::init() { - QList kernel = gaussianKernel(); - const int size = kernel.size(); - const int center = size / 2; - - QList offsets; - for (int i = 0; i < kernel.size(); i += 2) { - QVector4D vec4(0, 0, 0, 0); - - vec4.setX(kernel[i].x); - vec4.setY(kernel[i].x); - - if (i < kernel.size() - 1) { - vec4.setZ(kernel[i + 1].x); - vec4.setW(kernel[i + 1].x); - } - - offsets << vec4; - } - const bool gles = GLPlatform::instance()->isGLES(); const bool glsl_140 = !gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(1, 40); const bool core = glsl_140 || (gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)); QByteArray vertexSource; - QByteArray fragmentSource; + QByteArray fragmentDownSource; + QByteArray fragmentUpSource; + QByteArray fragmentCopySource; - const QByteArray attribute = core ? "in" : "attribute"; - const QByteArray varying_in = core ? (gles ? "in" : "noperspective in") : "varying"; - const QByteArray varying_out = core ? (gles ? "out" : "noperspective out") : "varying"; - const QByteArray texture2D = core ? "texture" : "texture2D"; - const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; + const QByteArray attribute = core ? "in" : "attribute"; + const QByteArray texture2D = core ? "texture" : "texture2D"; + const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; - // Vertex shader - // =================================================================== - QTextStream stream(&vertexSource); + QString glHeaderString; if (gles) { if (core) { - stream << "#version 300 es\n\n"; + glHeaderString += "#version 300 es\n\n"; } - stream << "precision highp float;\n"; + + glHeaderString += "precision highp float;\n"; } else if (glsl_140) { - stream << "#version 140\n\n"; + glHeaderString += "#version 140\n\n"; } - stream << "uniform mat4 modelViewProjectionMatrix;\n"; - stream << "uniform mat4 textureMatrix;\n"; - stream << "uniform vec2 pixelSize;\n\n"; - stream << attribute << " vec4 vertex;\n\n"; - stream << varying_out << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n"; - stream << "\n"; - stream << "void main(void)\n"; - stream << "{\n"; - stream << " vec4 center = vec4(textureMatrix * vertex).stst;\n"; - stream << " vec4 ps = pixelSize.stst;\n\n"; - for (int i = 0; i < offsets.size(); i++) { - stream << " samplePos[" << i << "] = center + ps * vec4(" - << offsets[i].x() << ", " << offsets[i].y() << ", " - << offsets[i].z() << ", " << offsets[i].w() << ");\n"; + QString glUniformString = "uniform sampler2D texUnit;\n" + "uniform float offset;\n" + "uniform vec2 renderTextureSize;\n" + "uniform vec2 halfpixel;\n"; + + if (core) { + glUniformString += "out vec4 fragColor;\n\n"; } - stream << "\n"; - stream << " gl_Position = modelViewProjectionMatrix * vertex;\n"; - stream << "}\n"; - stream.flush(); - // Fragment shader + + // Vertex shader // =================================================================== - QTextStream stream2(&fragmentSource); + QTextStream streamVert(&vertexSource); - if (gles) { - if (core) { - stream2 << "#version 300 es\n\n"; - } - stream2 << "precision highp float;\n"; - } else if (glsl_140) { - stream2 << "#version 140\n\n"; - } + streamVert << glHeaderString; + + streamVert << "uniform mat4 modelViewProjectionMatrix;\n"; + streamVert << attribute << " vec4 vertex;\n\n"; + streamVert << "\n"; + streamVert << "void main(void)\n"; + streamVert << "{\n"; + streamVert << " gl_Position = modelViewProjectionMatrix * vertex;\n"; + streamVert << "}\n"; + + streamVert.flush(); + + // Fragment shader (Dual Kawase Blur) - Downsample + // =================================================================== + QTextStream streamFragDown(&fragmentDownSource); + + streamFragDown << glHeaderString << glUniformString; + + streamFragDown << "void main(void)\n"; + streamFragDown << "{\n"; + streamFragDown << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragDown << " \n"; + streamFragDown << " vec4 sum = " << texture2D << "(texUnit, uv) * 4.0;\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + halfpixel.xy * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " sum += " << texture2D << "(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset);\n"; + streamFragDown << " \n"; + streamFragDown << " " << fragColor << " = sum / 8.0;\n"; + streamFragDown << "}\n"; + + streamFragDown.flush(); + + // Fragment shader (Dual Kawase Blur) - Upsample + // =================================================================== + QTextStream streamFragUp(&fragmentUpSource); + + streamFragUp << glHeaderString << glUniformString; + + streamFragUp << "void main(void)\n"; + streamFragUp << "{\n"; + streamFragUp << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragUp << " \n"; + streamFragUp << " vec4 sum = " << texture2D << "(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n"; + streamFragUp << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n"; + streamFragUp << " \n"; + streamFragUp << " " << fragColor << " = sum / 12.0;\n"; + streamFragUp << "}\n"; + + streamFragUp.flush(); + + // Fragment shader - Copy texture + // =================================================================== + QTextStream streamFragCopy(&fragmentCopySource); - stream2 << "uniform sampler2D texUnit;\n"; - stream2 << varying_in << " vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\n"; + streamFragCopy << glHeaderString; - for (int i = 0; i <= center; i++) - stream2 << "const float kernel" << i << " = " << kernel[i].g << ";\n"; - stream2 << "\n"; + streamFragCopy << "uniform sampler2D texUnit;\n"; + streamFragCopy << "uniform vec2 renderTextureSize;\n"; + streamFragCopy << "uniform vec4 blurRect;\n"; if (core) - stream2 << "out vec4 fragColor;\n\n"; - - stream2 << "void main(void)\n"; - stream2 << "{\n"; - stream2 << " vec4 sum = " << texture2D << "(texUnit, samplePos[0].st) * kernel0;\n"; - for (int i = 1, j = -center + 1; i < size; i++, j++) - stream2 << " sum = sum + " << texture2D << "(texUnit, samplePos[" << i / 2 - << ((i % 2) ? "].pq)" : "].st)") << " * kernel" << center - qAbs(j) << ";\n"; - stream2 << " " << fragColor << " = sum;\n"; - stream2 << "}\n"; - stream2.flush(); - - shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource); - if (shader->isValid()) { - pixelSizeLocation = shader->uniformLocation("pixelSize"); - textureMatrixLocation = shader->uniformLocation("textureMatrix"); - mvpMatrixLocation = shader->uniformLocation("modelViewProjectionMatrix"); + streamFragCopy << "out vec4 fragColor;\n\n"; + + streamFragCopy << "void main(void)\n"; + streamFragCopy << "{\n"; + streamFragCopy << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n"; + streamFragCopy << " " << fragColor << " = " << texture2D << "(texUnit, clamp(uv, blurRect.xy, blurRect.zw));\n"; + streamFragCopy << "}\n"; + + streamFragCopy.flush(); + + + m_shaderDownsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentDownSource); + m_shaderUpsample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentUpSource); + m_shaderCopysample = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentCopySource); + + bool areShadersValid = m_shaderDownsample->isValid() && m_shaderUpsample->isValid() && m_shaderCopysample->isValid(); + setIsValid(areShadersValid); + + if (areShadersValid) { + m_mvpMatrixLocationDownsample = m_shaderDownsample->uniformLocation("modelViewProjectionMatrix"); + m_offsetLocationDownsample = m_shaderDownsample->uniformLocation("offset"); + m_renderTextureSizeLocationDownsample = m_shaderDownsample->uniformLocation("renderTextureSize"); + m_halfpixelLocationDownsample = m_shaderDownsample->uniformLocation("halfpixel"); + + m_mvpMatrixLocationUpsample = m_shaderUpsample->uniformLocation("modelViewProjectionMatrix"); + m_offsetLocationUpsample = m_shaderUpsample->uniformLocation("offset"); + m_renderTextureSizeLocationUpsample = m_shaderUpsample->uniformLocation("renderTextureSize"); + m_halfpixelLocationUpsample = m_shaderUpsample->uniformLocation("halfpixel"); + + m_mvpMatrixLocationCopysample = m_shaderCopysample->uniformLocation("modelViewProjectionMatrix"); + m_renderTextureSizeLocationCopysample = m_shaderCopysample->uniformLocation("renderTextureSize"); + m_blurRectLocationCopysample = m_shaderCopysample->uniformLocation("blurRect"); QMatrix4x4 modelViewProjection; const QSize screenSize = effects->virtualScreenSize(); modelViewProjection.ortho(0, screenSize.width(), screenSize.height(), 0, 0, 65535); - ShaderManager::instance()->pushShader(shader); - shader->setUniform(textureMatrixLocation, QMatrix4x4()); - shader->setUniform(mvpMatrixLocation, modelViewProjection); + + //Add default values to the uniforms of the shaders + ShaderManager::instance()->pushShader(m_shaderDownsample); + m_shaderDownsample->setUniform(m_mvpMatrixLocationDownsample, modelViewProjection); + m_shaderDownsample->setUniform(m_offsetLocationDownsample, float(1.0)); + m_shaderDownsample->setUniform(m_renderTextureSizeLocationDownsample, QVector2D(1.0, 1.0)); + m_shaderDownsample->setUniform(m_halfpixelLocationDownsample, QVector2D(1.0, 1.0)); ShaderManager::instance()->popShader(); - } - setIsValid(shader->isValid()); + ShaderManager::instance()->pushShader(m_shaderUpsample); + m_shaderUpsample->setUniform(m_mvpMatrixLocationUpsample, modelViewProjection); + m_shaderUpsample->setUniform(m_offsetLocationUpsample, float(1.0)); + m_shaderUpsample->setUniform(m_renderTextureSizeLocationUpsample, QVector2D(1.0, 1.0)); + m_shaderUpsample->setUniform(m_halfpixelLocationUpsample, QVector2D(1.0, 1.0)); + ShaderManager::instance()->popShader(); + + ShaderManager::instance()->pushShader(m_shaderCopysample); + m_shaderCopysample->setUniform(m_mvpMatrixLocationCopysample, modelViewProjection); + m_shaderCopysample->setUniform(m_renderTextureSizeLocationCopysample, QVector2D(1.0, 1.0)); + m_shaderCopysample->setUniform(m_blurRectLocationCopysample, QVector4D(1.0, 1.0, 1.0, 1.0)); + ShaderManager::instance()->popShader(); + + m_activeSampleType = -1; + } } diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h old mode 100644 new mode 100755 index 49e9c0b86..a69811a1f --- a/effects/blur/blurshader.h +++ b/effects/blur/blurshader.h @@ -1,120 +1,132 @@ /* * Copyright © 2010 Fredrik Höglund + * Copyright © 2018 Alex Nemeth * * 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 +#include +#include + class QMatrix4x4; namespace KWin { -struct KernelValue -{ - KernelValue() {} - KernelValue(float x, float g) : x(x), g(g) {} - bool operator < (const KernelValue &other) const { return x < other.x; } - - float x; - float g; -}; - class BlurShader { public: 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 setOffset(float offset) = 0; + virtual void setTargetSize(QSize renderTextureSize) = 0; + virtual void setBlurRect(QRect blurRect, QSize screenSize) = 0; + + enum SampleType { + DownSampleType, + UpSampleType, + CopySampleType + }; - virtual void bind() = 0; + virtual void bind(SampleType sampleType) = 0; virtual void unbind() = 0; protected: - float gaussian(float x, float sigma) const; - QList gaussianKernel() const; void setIsValid(bool value) { mValid = value; } virtual void init() = 0; virtual void reset() = 0; - virtual int maxKernelSize() const = 0; private: - int mRadius; - Qt::Orientation mDirection; bool mValid; }; // ---------------------------------------------------------------------------- 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); + void bind(SampleType sampleType) override final; + void unbind() override final; + void setModelViewProjectionMatrix(const QMatrix4x4 &matrix) override final; + void setOffset(float offset) override final; + void setTargetSize(QSize renderTextureSize) override final; + void setBlurRect(QRect blurRect, QSize screenSize) override final; protected: - void init(); - void reset(); - int maxKernelSize() const; + void init() override final; + void reset() override final; private: - GLShader *shader; - int mvpMatrixLocation; - int textureMatrixLocation; - int pixelSizeLocation; + GLShader *m_shaderDownsample = nullptr; + GLShader *m_shaderUpsample = nullptr; + GLShader *m_shaderCopysample = nullptr; + + int m_mvpMatrixLocationDownsample; + int m_offsetLocationDownsample; + int m_renderTextureSizeLocationDownsample; + int m_halfpixelLocationDownsample; + + int m_mvpMatrixLocationUpsample; + int m_offsetLocationUpsample; + int m_renderTextureSizeLocationUpsample; + int m_halfpixelLocationUpsample; + + int m_mvpMatrixLocationCopysample; + int m_renderTextureSizeLocationCopysample; + int m_blurRectLocationCopysample; + + + //Caching uniform values to aviod unnecessary setUniform calls + int m_activeSampleType; + + float m_offsetDownsample; + QMatrix4x4 m_matrixDownsample; + QSize m_renderTextureSizeDownsample; + + float m_offsetUpsample; + QMatrix4x4 m_matrixUpsample; + QSize m_renderTextureSizeUpsample; + + QMatrix4x4 m_matrixCopysample; + QSize m_renderTextureSizeCopysample; + QRect m_blurRectCopysample; + }; } // namespace KWin #endif - diff --git a/libkwineffects/kwinglutils.cpp b/libkwineffects/kwinglutils.cpp index d1def249a..24a14de09 100644 --- a/libkwineffects/kwinglutils.cpp +++ b/libkwineffects/kwinglutils.cpp @@ -1,2270 +1,2333 @@ /******************************************************************** 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 "kwineffects.h" #include "kwinglplatform.h" #include "logging_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_GLRENDERTARGET 0 #ifdef __GNUC__ # define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0) #else # define likely(x) (x) # define unlikely(x) (x) #endif namespace KWin { // Variables // List of all supported GL extensions static QList glExtensions; // Functions void initGL(std::function resolveFunction) { // Get list of supported OpenGL extensions if (hasGLVersion(3, 0)) { int count; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (int i = 0; i < count; i++) { const QByteArray name = (const char *) glGetStringi(GL_EXTENSIONS, i); glExtensions << name; } } else glExtensions = QByteArray((const char*)glGetString(GL_EXTENSIONS)).split(' '); // handle OpenGL extensions functions glResolveFunctions(resolveFunction); GLTexturePrivate::initStatic(); GLRenderTarget::initStatic(); GLVertexBuffer::initStatic(); } void cleanupGL() { ShaderManager::cleanup(); GLTexturePrivate::cleanup(); GLRenderTarget::cleanup(); GLVertexBuffer::cleanup(); GLPlatform::cleanup(); glExtensions.clear(); } bool hasGLVersion(int major, int minor, int release) { return GLPlatform::instance()->glVersion() >= kVersionNumber(major, minor, release); } bool hasGLExtension(const QByteArray &extension) { return glExtensions.contains(extension); } QList openGLExtensions() { return glExtensions; } static QString formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return QStringLiteral("GL_NO_ERROR"); case GL_INVALID_ENUM: return QStringLiteral("GL_INVALID_ENUM"); case GL_INVALID_VALUE: return QStringLiteral("GL_INVALID_VALUE"); case GL_INVALID_OPERATION: return QStringLiteral("GL_INVALID_OPERATION"); case GL_STACK_OVERFLOW: return QStringLiteral("GL_STACK_OVERFLOW"); case GL_STACK_UNDERFLOW: return QStringLiteral("GL_STACK_UNDERFLOW"); case GL_OUT_OF_MEMORY: return QStringLiteral("GL_OUT_OF_MEMORY"); default: return QLatin1String("0x") + QString::number(err, 16); } } bool checkGLError(const char* txt) { GLenum err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; return true; } bool hasError = false; while (err != GL_NO_ERROR) { qCWarning(LIBKWINGLUTILS) << "GL error (" << txt << "): " << formatGLError(err); hasError = true; err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; break; } } return hasError; } //**************************************** // GLShader //**************************************** GLShader::GLShader(unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); } GLShader::GLShader(const QString& vertexfile, const QString& fragmentfile, unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); 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)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << vertexFile << "for reading!"; return false; } const QByteArray vertexSource = vf.readAll(); QFile ff(fragmentFile); if (!ff.open(QIODevice::ReadOnly)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << fragmentFile << "for reading!"; return false; } const QByteArray fragmentSource = ff.readAll(); return load(vertexSource, fragmentSource); } bool GLShader::link() { // Be optimistic mValid = true; 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) { qCCritical(LIBKWINGLUTILS) << "Failed to link shader:" << endl << log; mValid = false; } else if (length > 0) { qCDebug(LIBKWINGLUTILS) << "Shader link log:" << log; } return mValid; } const QByteArray GLShader::prepareSource(GLenum shaderType, const QByteArray &source) const { Q_UNUSED(shaderType) // Prepare the source code QByteArray ba; if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() < kVersionNumber(3, 0)) { ba.append("precision highp float;\n"); } if (ShaderManager::instance()->isShaderDebug()) { ba.append("#define KWIN_SHADER_DEBUG 1\n"); } ba.append(source); if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)) { ba.replace("#version 140", "#version 300 es\n\nprecision highp float;\n"); } return ba; } bool GLShader::compile(GLuint program, GLenum shaderType, const QByteArray &source) const { GLuint shader = glCreateShader(shaderType); QByteArray preparedSource = prepareSource(shaderType, source); const char* src = preparedSource.constData(); glShaderSource(shader, 1, &src, nullptr); // 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"); qCCritical(LIBKWINGLUTILS) << "Failed to compile" << typeName << "shader:" << endl << log; } else if (length > 0) qCDebug(LIBKWINGLUTILS) << "Shader compile log:" << log; if (status != 0) glAttachShader(program, shader); glDeleteShader(shader); return status != 0; } bool GLShader::load(const QByteArray &vertexSource, const QByteArray &fragmentSource) { // Make sure shaders are actually supported if (!(GLPlatform::instance()->supports(GLSL) && // we lack shader branching for Texture2DRectangle everywhere - and it's probably not worth it GLPlatform::instance()->supports(TextureNPOT))) { qCCritical(LIBKWINGLUTILS) << "Shaders are not supported"; return false; } mValid = false; // Compile the vertex shader if (!vertexSource.isEmpty()) { bool success = compile(mProgram, GL_VERTEX_SHADER, vertexSource); if (!success) return false; } // Compile the fragment shader if (!fragmentSource.isEmpty()) { bool success = compile(mProgram, GL_FRAGMENT_SHADER, fragmentSource); if (!success) return false; } if (mExplicitLinking) return true; // link() sets mValid return link(); } void GLShader::bindAttributeLocation(const char *name, int index) { glBindAttribLocation(mProgram, index, name); } void GLShader::bindFragDataLocation(const char *name, int index) { if (!GLPlatform::instance()->isGLES() && (hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_EXT_gpu_shader4")))) glBindFragDataLocation(mProgram, index, name); } 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[ModelViewProjectionMatrix] = uniformLocation("modelViewProjectionMatrix"); mMatrixLocation[WindowTransformation] = uniformLocation("windowTransformation"); mMatrixLocation[ScreenTransformation] = uniformLocation("screenTransformation"); mVec2Location[Offset] = uniformLocation("offset"); mVec4Location[ModulationConstant] = uniformLocation("modulation"); mFloatLocation[Saturation] = uniformLocation("saturation"); mColorLocation[Color] = uniformLocation("geometryColor"); 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(GLShader::ColorUniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mColorLocation[uniform], value); } bool GLShader::setUniform(GLShader::ColorUniform uniform, const QColor &value) { resolveLocations(); return setUniform(mColorLocation[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 auto *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]; glGetnUniformfv(mProgram, location, sizeof(m), 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 = nullptr; ShaderManager *ShaderManager::instance() { if (!s_shaderManager) { s_shaderManager = new ShaderManager(); } return s_shaderManager; } void ShaderManager::cleanup() { delete s_shaderManager; s_shaderManager = nullptr; } ShaderManager::ShaderManager() { m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; const qint64 coreVersionNumber = GLPlatform::instance()->isGLES() ? kVersionNumber(3, 0) : kVersionNumber(1, 40); if (GLPlatform::instance()->glslVersion() >= coreVersionNumber) { m_resourcePath = QStringLiteral(":/effect-shaders-1.40/"); } else { m_resourcePath = QStringLiteral(":/effect-shaders-1.10/"); } } ShaderManager::~ShaderManager() { while (!m_boundShaders.isEmpty()) { popShader(); } qDeleteAll(m_shaderHash); m_shaderHash.clear(); } static bool fuzzyCompare(const QVector4D &lhs, const QVector4D &rhs) { const float epsilon = 1.0f / 255.0f; return lhs[0] >= rhs[0] - epsilon && lhs[0] <= rhs[0] + epsilon && lhs[1] >= rhs[1] - epsilon && lhs[1] <= rhs[1] + epsilon && lhs[2] >= rhs[2] - epsilon && lhs[2] <= rhs[2] + epsilon && lhs[3] >= rhs[3] - epsilon && lhs[3] <= rhs[3] + epsilon; } static bool checkPixel(int x, int y, const QVector4D &expected, const char *file, int line) { uint8_t data[4]; glReadnPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, 4, data); const QVector4D pixel{data[0] / 255.f, data[1] / 255.f, data[2] / 255.f, data[3] / 255.f}; if (fuzzyCompare(pixel, expected)) return true; QMessageLogger(file, line, nullptr).warning() << "Pixel was" << pixel << "expected" << expected; return false; } #define CHECK_PIXEL(x, y, expected) \ checkPixel(x, y, expected, __FILE__, __LINE__) static QVector4D adjustSaturation(const QVector4D &color, float saturation) { const float gray = QVector3D::dotProduct(color.toVector3D(), {0.2126, 0.7152, 0.0722}); return QVector4D{gray, gray, gray, color.w()} * (1.0f - saturation) + color * saturation; } bool ShaderManager::selfTest() { bool pass = true; if (!GLRenderTarget::supported()) { qCWarning(LIBKWINGLUTILS) << "Framebuffer objects not supported - skipping shader tests"; return true; } if (GLPlatform::instance()->isNvidia() && GLPlatform::instance()->glRendererString().contains("Quadro")) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Quadro hardware"; return true; } if (GLPlatform::instance()->isMesaDriver() && GLPlatform::instance()->mesaVersion() >= kVersionNumber(17, 0)) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Mesa drivers"; return true; } // Create the source texture QImage image(2, 2, QImage::Format_ARGB32_Premultiplied); image.setPixel(0, 0, 0xffff0000); // Red image.setPixel(1, 0, 0xff00ff00); // Green image.setPixel(0, 1, 0xff0000ff); // Blue image.setPixel(1, 1, 0xffffffff); // White GLTexture src(image); src.setFilter(GL_NEAREST); // Create the render target GLTexture dst(GL_RGBA8, 32, 32); GLRenderTarget fbo(dst); GLRenderTarget::pushRenderTarget(&fbo); // Set up the vertex buffer GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); const GLVertexAttrib attribs[] { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); GLVertex2D *verts = (GLVertex2D*) vbo->map(6 * sizeof(GLVertex2D)); verts[0] = GLVertex2D{{0, 0}, {0, 0}}; // Top left verts[1] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[2] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[3] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[4] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[5] = GLVertex2D{{32, 32}, {1, 1}}; // Bottom right vbo->unmap(); vbo->bindArrays(); glViewport(0, 0, 32, 32); glClearColor(0, 0, 0, 0); // Set up the projection matrix QMatrix4x4 matrix; matrix.ortho(QRect(0, 0, 32, 32)); // Bind the source texture src.bind(); const QVector4D red {1.0f, 0.0f, 0.0f, 1.0f}; const QVector4D green {0.0f, 1.0f, 0.0f, 1.0f}; const QVector4D blue {0.0f, 0.0f, 1.0f, 1.0f}; const QVector4D white {1.0f, 1.0f, 1.0f, 1.0f}; // Note: To see the line number in error messages, set // QT_MESSAGE_PATTERN="%{message} (%{file}:%{line})" // Test solid color GLShader *shader = pushShader(ShaderTrait::UniformColor); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Color, green); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, green) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, green) && pass; pass = CHECK_PIXEL(24, 8, green) && pass; } else { pass = false; } popShader(); // Test texture mapping shader = pushShader(ShaderTrait::MapTexture); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, blue) && pass; pass = CHECK_PIXEL(24, 8, white) && pass; } else { pass = false; } popShader(); // Test saturation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white, saturation)) && pass; } else { pass = false; } popShader(); // Test modulation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red * modulation) && pass; pass = CHECK_PIXEL(24, 24, green * modulation) && pass; pass = CHECK_PIXEL(8, 8, blue * modulation) && pass; pass = CHECK_PIXEL(24, 8, white * modulation) && pass; } else { pass = false; } popShader(); // Test saturation + modulation shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green * modulation, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white * modulation, saturation)) && pass; } else { pass = false; } popShader(); vbo->unbindArrays(); GLRenderTarget::popRenderTarget(); return pass; } QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray attribute, varying; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_140) stream << "#version 140\n\n"; } else { const bool glsl_es_300 = gl->glslVersion() >= kVersionNumber(3, 0); attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_es_300) stream << "#version 300 es\n\n"; } stream << attribute << " vec4 position;\n"; if (traits & ShaderTrait::MapTexture) { stream << attribute << " vec4 texcoord;\n\n"; stream << varying << " vec2 texcoord0;\n\n"; } else stream << "\n"; stream << "uniform mat4 modelViewProjectionMatrix;\n\n"; stream << "void main()\n{\n"; if (traits & ShaderTrait::MapTexture) stream << " texcoord0 = texcoord.st;\n"; stream << " gl_Position = modelViewProjectionMatrix * position;\n"; stream << "}\n"; stream.flush(); return source; } QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray varying, output, textureLookup; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); if (glsl_140) stream << "#version 140\n\n"; varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } else { const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0); if (glsl_es_300) stream << "#version 300 es\n\n"; // From the GLSL ES specification: // // "The fragment language has no default precision qualifier for floating point types." stream << "precision highp float;\n\n"; varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } if (traits & ShaderTrait::MapTexture) { stream << "uniform sampler2D sampler;\n"; if (traits & ShaderTrait::Modulate) stream << "uniform vec4 modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << "uniform float saturation;\n"; stream << "\n" << varying << " vec2 texcoord0;\n"; } else if (traits & ShaderTrait::UniformColor) stream << "uniform vec4 geometryColor;\n"; if (output != QByteArrayLiteral("gl_FragColor")) stream << "\nout vec4 " << output << ";\n"; stream << "\nvoid main(void)\n{\n"; if (traits & ShaderTrait::MapTexture) { if (traits & (ShaderTrait::Modulate | ShaderTrait::AdjustSaturation)) { stream << " vec4 texel = " << textureLookup << "(sampler, texcoord0);\n"; if (traits & ShaderTrait::Modulate) stream << " texel *= modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n"; stream << " " << output << " = texel;\n"; } else { stream << " " << output << " = " << textureLookup << "(sampler, texcoord0);\n"; } } else if (traits & ShaderTrait::UniformColor) stream << " " << output << " = geometryColor;\n"; stream << "}"; stream.flush(); return source; } GLShader *ShaderManager::generateShader(ShaderTraits traits) { return generateCustomShader(traits); } GLShader *ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource) { const QByteArray vertex = vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource; const QByteArray fragment = fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource; #if 0 qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << vertex; qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << fragment; qCDebug(LIBKWINGLUTILS) << "**************"; #endif GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertex, fragment); shader->bindAttributeLocation("position", VA_Position); shader->bindAttributeLocation("texcoord", VA_TexCoord); shader->bindFragDataLocation("fragColor", 0); shader->link(); return shader; } GLShader *ShaderManager::generateShaderFromResources(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile) { auto loadShaderFile = [this] (const QString &fileName) { QFile file(m_resourcePath + fileName); if (file.open(QIODevice::ReadOnly)) { return file.readAll(); } qCCritical(LIBKWINGLUTILS) << "Failed to read shader " << fileName; return QByteArray(); }; QByteArray vertexSource; QByteArray fragmentSource; if (!vertexFile.isEmpty()) { vertexSource = loadShaderFile(vertexFile); if (vertexSource.isEmpty()) { return new GLShader(); } } if (!fragmentFile.isEmpty()) { fragmentSource = loadShaderFile(fragmentFile); if (fragmentSource.isEmpty()) { return new GLShader(); } } return generateCustomShader(traits, vertexSource, fragmentSource); } GLShader *ShaderManager::shader(ShaderTraits traits) { GLShader *shader = m_shaderHash.value(traits); if (!shader) { shader = generateShader(traits); m_shaderHash.insert(traits, shader); } return shader; } GLShader *ShaderManager::getBoundShader() const { if (m_boundShaders.isEmpty()) { return nullptr; } else { return m_boundShaders.top(); } } bool ShaderManager::isShaderBound() const { return !m_boundShaders.isEmpty(); } bool ShaderManager::isShaderDebug() const { return m_debug; } GLShader *ShaderManager::pushShader(ShaderTraits traits) { GLShader *shader = this->shader(traits); pushShader(shader); 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(); } } void ShaderManager::bindFragDataLocations(GLShader *shader) { shader->bindFragDataLocation("fragColor", 0); } void ShaderManager::bindAttributeLocations(GLShader *shader) const { shader->bindAttributeLocation("vertex", VA_Position); shader->bindAttributeLocation("texCoord", VA_TexCoord); } GLShader *ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource) { GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertexSource, fragmentSource); bindAttributeLocations(shader); bindFragDataLocations(shader); shader->link(); return shader; } /*** GLRenderTarget ***/ bool GLRenderTarget::sSupported = false; bool GLRenderTarget::s_blitSupported = false; QStack GLRenderTarget::s_renderTargets = QStack(); QSize GLRenderTarget::s_virtualScreenSize; QRect GLRenderTarget::s_virtualScreenGeometry; qreal GLRenderTarget::s_virtualScreenScale = 1.0; GLint GLRenderTarget::s_virtualScreenViewport[4]; void GLRenderTarget::initStatic() { if (GLPlatform::instance()->isGLES()) { sSupported = true; s_blitSupported = hasGLVersion(3, 0); } else { sSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_object")); s_blitSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_blit")); } } void GLRenderTarget::cleanup() { Q_ASSERT(s_renderTargets.isEmpty()); sSupported = false; s_blitSupported = false; } bool GLRenderTarget::isRenderTargetBound() { return !s_renderTargets.isEmpty(); } bool GLRenderTarget::blitSupported() { return s_blitSupported; } void GLRenderTarget::pushRenderTarget(GLRenderTarget* target) { if (s_renderTargets.isEmpty()) { glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); } target->enable(); s_renderTargets.push(target); } +void GLRenderTarget::pushRenderTargets(QStack targets) +{ + if (s_renderTargets.isEmpty()) { + glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); + s_renderTargets = targets; + } else { + s_renderTargets.reserve(s_renderTargets.size() + targets.size()); + + /* + * Merging the two stacks. + * We cheat a little bit by using the inherited QVector functions. + * This is to not have the targets stack in reverse order without + * having to use a helper QStack first to reverse the order. + */ + while (!targets.isEmpty()) { + s_renderTargets.push(targets.first()); + targets.removeFirst(); + } + } + + s_renderTargets.top()->enable(); +} + GLRenderTarget* GLRenderTarget::popRenderTarget() { GLRenderTarget* ret = s_renderTargets.pop(); - ret->disable(); + ret->setTextureDirty(); if (!s_renderTargets.isEmpty()) { s_renderTargets.top()->enable(); } else { + ret->disable(); glViewport (s_virtualScreenViewport[0], s_virtualScreenViewport[1], s_virtualScreenViewport[2], s_virtualScreenViewport[3]); } + return ret; } +GLRenderTarget::GLRenderTarget() +{ + // Reset variables + mValid = false; + mTexture = GLTexture(); +} + GLRenderTarget::GLRenderTarget(const GLTexture& color) { // Reset variables mValid = false; mTexture = color; // Make sure FBO is supported if (sSupported && !mTexture.isNull()) { initFBO(); } else qCCritical(LIBKWINGLUTILS) << "Render targets aren't supported!"; } GLRenderTarget::~GLRenderTarget() { if (mValid) { glDeleteFramebuffers(1, &mFramebuffer); } } bool GLRenderTarget::enable() { + if (!mValid) { + initFBO(); + } + if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't enable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); glViewport(0, 0, mTexture.width(), mTexture.height()); mTexture.setDirty(); return true; } bool GLRenderTarget::disable() { + if (!mValid) { + initFBO(); + } + if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't disable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); 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 QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: // There are no images attached to the framebuffer return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case GL_FRAMEBUFFER_UNSUPPORTED: // A format or the combination of formats of the attachments is unsupported return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED"); case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: // Not all attached images have the same width and height return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: // The color attachments don't have the same format return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: // The attachments don't have the same number of samples return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: // The draw buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: // The read buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); default: return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")"); } } void GLRenderTarget::initFBO() { #if DEBUG_GLRENDERTARGET GLenum err = glGetError(); if (err != GL_NO_ERROR) qCCritical(LIBKWINGLUTILS) << "Error status when entering GLRenderTarget::initFBO: " << formatGLError(err); #endif glGenFramebuffers(1, &mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glGenFramebuffers failed: " << formatGLError(err); return; } #endif glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glBindFramebuffer failed: " << formatGLError(err); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "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) qCCritical(LIBKWINGLUTILS) << "glCheckFramebufferStatus failed: " << formatGLError(glGetError()); else qCCritical(LIBKWINGLUTILS) << "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; } + + if (!mValid) { + initFBO(); + } + GLRenderTarget::pushRenderTarget(this); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); const QRect s = source.isNull() ? s_virtualScreenGeometry : source; const QRect d = destination.isNull() ? QRect(0, 0, mTexture.width(), mTexture.height()) : destination; glBlitFramebuffer((s.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() + s.y() - s.height()) * s_virtualScreenScale, (s.x() - s_virtualScreenGeometry.x() + s.width()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() - s_virtualScreenGeometry.y() + s.y()) * s_virtualScreenScale, d.x(), mTexture.height() - d.y() - d.height(), d.x() + d.width(), mTexture.height() - d.y(), GL_COLOR_BUFFER_BIT, filter); GLRenderTarget::popRenderTarget(); } void GLRenderTarget::attachTexture(const GLTexture& target) { - if (!mValid || mTexture.texture() == target.texture()) { + if (!mValid) { + initFBO(); + } + + if (mTexture.texture() == target.texture()) { return; } pushRenderTarget(this); mTexture = target; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); popRenderTarget(); } +void GLRenderTarget::detachTexture() +{ + if (mTexture.isNull()) { + return; + } + + pushRenderTarget(this); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + mTexture.target(), 0, 0); + + popRenderTarget(); +} + // ------------------------------------------------------------------ static const uint16_t indices[] = { 1, 0, 3, 3, 2, 1, 5, 4, 7, 7, 6, 5, 9, 8, 11, 11, 10, 9, 13, 12, 15, 15, 14, 13, 17, 16, 19, 19, 18, 17, 21, 20, 23, 23, 22, 21, 25, 24, 27, 27, 26, 25, 29, 28, 31, 31, 30, 29, 33, 32, 35, 35, 34, 33, 37, 36, 39, 39, 38, 37, 41, 40, 43, 43, 42, 41, 45, 44, 47, 47, 46, 45, 49, 48, 51, 51, 50, 49, 53, 52, 55, 55, 54, 53, 57, 56, 59, 59, 58, 57, 61, 60, 63, 63, 62, 61, 65, 64, 67, 67, 66, 65, 69, 68, 71, 71, 70, 69, 73, 72, 75, 75, 74, 73, 77, 76, 79, 79, 78, 77, 81, 80, 83, 83, 82, 81, 85, 84, 87, 87, 86, 85, 89, 88, 91, 91, 90, 89, 93, 92, 95, 95, 94, 93, 97, 96, 99, 99, 98, 97, 101, 100, 103, 103, 102, 101, 105, 104, 107, 107, 106, 105, 109, 108, 111, 111, 110, 109, 113, 112, 115, 115, 114, 113, 117, 116, 119, 119, 118, 117, 121, 120, 123, 123, 122, 121, 125, 124, 127, 127, 126, 125, 129, 128, 131, 131, 130, 129, 133, 132, 135, 135, 134, 133, 137, 136, 139, 139, 138, 137, 141, 140, 143, 143, 142, 141, 145, 144, 147, 147, 146, 145, 149, 148, 151, 151, 150, 149, 153, 152, 155, 155, 154, 153, 157, 156, 159, 159, 158, 157, 161, 160, 163, 163, 162, 161, 165, 164, 167, 167, 166, 165, 169, 168, 171, 171, 170, 169, 173, 172, 175, 175, 174, 173, 177, 176, 179, 179, 178, 177, 181, 180, 183, 183, 182, 181, 185, 184, 187, 187, 186, 185, 189, 188, 191, 191, 190, 189, 193, 192, 195, 195, 194, 193, 197, 196, 199, 199, 198, 197, 201, 200, 203, 203, 202, 201, 205, 204, 207, 207, 206, 205, 209, 208, 211, 211, 210, 209, 213, 212, 215, 215, 214, 213, 217, 216, 219, 219, 218, 217, 221, 220, 223, 223, 222, 221, 225, 224, 227, 227, 226, 225, 229, 228, 231, 231, 230, 229, 233, 232, 235, 235, 234, 233, 237, 236, 239, 239, 238, 237, 241, 240, 243, 243, 242, 241, 245, 244, 247, 247, 246, 245, 249, 248, 251, 251, 250, 249, 253, 252, 255, 255, 254, 253, 257, 256, 259, 259, 258, 257, 261, 260, 263, 263, 262, 261, 265, 264, 267, 267, 266, 265, 269, 268, 271, 271, 270, 269, 273, 272, 275, 275, 274, 273, 277, 276, 279, 279, 278, 277, 281, 280, 283, 283, 282, 281, 285, 284, 287, 287, 286, 285, 289, 288, 291, 291, 290, 289, 293, 292, 295, 295, 294, 293, 297, 296, 299, 299, 298, 297, 301, 300, 303, 303, 302, 301, 305, 304, 307, 307, 306, 305, 309, 308, 311, 311, 310, 309, 313, 312, 315, 315, 314, 313, 317, 316, 319, 319, 318, 317, 321, 320, 323, 323, 322, 321, 325, 324, 327, 327, 326, 325, 329, 328, 331, 331, 330, 329, 333, 332, 335, 335, 334, 333, 337, 336, 339, 339, 338, 337, 341, 340, 343, 343, 342, 341, 345, 344, 347, 347, 346, 345, 349, 348, 351, 351, 350, 349, 353, 352, 355, 355, 354, 353, 357, 356, 359, 359, 358, 357, 361, 360, 363, 363, 362, 361, 365, 364, 367, 367, 366, 365, 369, 368, 371, 371, 370, 369, 373, 372, 375, 375, 374, 373, 377, 376, 379, 379, 378, 377, 381, 380, 383, 383, 382, 381, 385, 384, 387, 387, 386, 385, 389, 388, 391, 391, 390, 389, 393, 392, 395, 395, 394, 393, 397, 396, 399, 399, 398, 397, 401, 400, 403, 403, 402, 401, 405, 404, 407, 407, 406, 405, 409, 408, 411, 411, 410, 409, 413, 412, 415, 415, 414, 413, 417, 416, 419, 419, 418, 417, 421, 420, 423, 423, 422, 421, 425, 424, 427, 427, 426, 425, 429, 428, 431, 431, 430, 429, 433, 432, 435, 435, 434, 433, 437, 436, 439, 439, 438, 437, 441, 440, 443, 443, 442, 441, 445, 444, 447, 447, 446, 445, 449, 448, 451, 451, 450, 449, 453, 452, 455, 455, 454, 453, 457, 456, 459, 459, 458, 457, 461, 460, 463, 463, 462, 461, 465, 464, 467, 467, 466, 465, 469, 468, 471, 471, 470, 469, 473, 472, 475, 475, 474, 473, 477, 476, 479, 479, 478, 477, 481, 480, 483, 483, 482, 481, 485, 484, 487, 487, 486, 485, 489, 488, 491, 491, 490, 489, 493, 492, 495, 495, 494, 493, 497, 496, 499, 499, 498, 497, 501, 500, 503, 503, 502, 501, 505, 504, 507, 507, 506, 505, 509, 508, 511, 511, 510, 509, 513, 512, 515, 515, 514, 513, 517, 516, 519, 519, 518, 517, 521, 520, 523, 523, 522, 521, 525, 524, 527, 527, 526, 525, 529, 528, 531, 531, 530, 529, 533, 532, 535, 535, 534, 533, 537, 536, 539, 539, 538, 537, 541, 540, 543, 543, 542, 541, 545, 544, 547, 547, 546, 545, 549, 548, 551, 551, 550, 549, 553, 552, 555, 555, 554, 553, 557, 556, 559, 559, 558, 557, 561, 560, 563, 563, 562, 561, 565, 564, 567, 567, 566, 565, 569, 568, 571, 571, 570, 569, 573, 572, 575, 575, 574, 573, 577, 576, 579, 579, 578, 577, 581, 580, 583, 583, 582, 581, 585, 584, 587, 587, 586, 585, 589, 588, 591, 591, 590, 589, 593, 592, 595, 595, 594, 593, 597, 596, 599, 599, 598, 597, 601, 600, 603, 603, 602, 601, 605, 604, 607, 607, 606, 605, 609, 608, 611, 611, 610, 609, 613, 612, 615, 615, 614, 613, 617, 616, 619, 619, 618, 617, 621, 620, 623, 623, 622, 621, 625, 624, 627, 627, 626, 625, 629, 628, 631, 631, 630, 629, 633, 632, 635, 635, 634, 633, 637, 636, 639, 639, 638, 637, 641, 640, 643, 643, 642, 641, 645, 644, 647, 647, 646, 645, 649, 648, 651, 651, 650, 649, 653, 652, 655, 655, 654, 653, 657, 656, 659, 659, 658, 657, 661, 660, 663, 663, 662, 661, 665, 664, 667, 667, 666, 665, 669, 668, 671, 671, 670, 669, 673, 672, 675, 675, 674, 673, 677, 676, 679, 679, 678, 677, 681, 680, 683, 683, 682, 681, 685, 684, 687, 687, 686, 685, 689, 688, 691, 691, 690, 689, 693, 692, 695, 695, 694, 693, 697, 696, 699, 699, 698, 697, 701, 700, 703, 703, 702, 701, 705, 704, 707, 707, 706, 705, 709, 708, 711, 711, 710, 709, 713, 712, 715, 715, 714, 713, 717, 716, 719, 719, 718, 717, 721, 720, 723, 723, 722, 721, 725, 724, 727, 727, 726, 725, 729, 728, 731, 731, 730, 729, 733, 732, 735, 735, 734, 733, 737, 736, 739, 739, 738, 737, 741, 740, 743, 743, 742, 741, 745, 744, 747, 747, 746, 745, 749, 748, 751, 751, 750, 749, 753, 752, 755, 755, 754, 753, 757, 756, 759, 759, 758, 757, 761, 760, 763, 763, 762, 761, 765, 764, 767, 767, 766, 765, 769, 768, 771, 771, 770, 769, 773, 772, 775, 775, 774, 773, 777, 776, 779, 779, 778, 777, 781, 780, 783, 783, 782, 781, 785, 784, 787, 787, 786, 785, 789, 788, 791, 791, 790, 789, 793, 792, 795, 795, 794, 793, 797, 796, 799, 799, 798, 797, 801, 800, 803, 803, 802, 801, 805, 804, 807, 807, 806, 805, 809, 808, 811, 811, 810, 809, 813, 812, 815, 815, 814, 813, 817, 816, 819, 819, 818, 817, 821, 820, 823, 823, 822, 821, 825, 824, 827, 827, 826, 825, 829, 828, 831, 831, 830, 829, 833, 832, 835, 835, 834, 833, 837, 836, 839, 839, 838, 837, 841, 840, 843, 843, 842, 841, 845, 844, 847, 847, 846, 845, 849, 848, 851, 851, 850, 849, 853, 852, 855, 855, 854, 853, 857, 856, 859, 859, 858, 857, 861, 860, 863, 863, 862, 861, 865, 864, 867, 867, 866, 865, 869, 868, 871, 871, 870, 869, 873, 872, 875, 875, 874, 873, 877, 876, 879, 879, 878, 877, 881, 880, 883, 883, 882, 881, 885, 884, 887, 887, 886, 885, 889, 888, 891, 891, 890, 889, 893, 892, 895, 895, 894, 893, 897, 896, 899, 899, 898, 897, 901, 900, 903, 903, 902, 901, 905, 904, 907, 907, 906, 905, 909, 908, 911, 911, 910, 909, 913, 912, 915, 915, 914, 913, 917, 916, 919, 919, 918, 917, 921, 920, 923, 923, 922, 921, 925, 924, 927, 927, 926, 925, 929, 928, 931, 931, 930, 929, 933, 932, 935, 935, 934, 933, 937, 936, 939, 939, 938, 937, 941, 940, 943, 943, 942, 941, 945, 944, 947, 947, 946, 945, 949, 948, 951, 951, 950, 949, 953, 952, 955, 955, 954, 953, 957, 956, 959, 959, 958, 957, 961, 960, 963, 963, 962, 961, 965, 964, 967, 967, 966, 965, 969, 968, 971, 971, 970, 969, 973, 972, 975, 975, 974, 973, 977, 976, 979, 979, 978, 977, 981, 980, 983, 983, 982, 981, 985, 984, 987, 987, 986, 985, 989, 988, 991, 991, 990, 989, 993, 992, 995, 995, 994, 993, 997, 996, 999, 999, 998, 997, 1001, 1000, 1003, 1003, 1002, 1001, 1005, 1004, 1007, 1007, 1006, 1005, 1009, 1008, 1011, 1011, 1010, 1009, 1013, 1012, 1015, 1015, 1014, 1013, 1017, 1016, 1019, 1019, 1018, 1017, 1021, 1020, 1023, 1023, 1022, 1021, 1025, 1024, 1027, 1027, 1026, 1025, 1029, 1028, 1031, 1031, 1030, 1029, 1033, 1032, 1035, 1035, 1034, 1033, 1037, 1036, 1039, 1039, 1038, 1037, 1041, 1040, 1043, 1043, 1042, 1041, 1045, 1044, 1047, 1047, 1046, 1045, 1049, 1048, 1051, 1051, 1050, 1049, 1053, 1052, 1055, 1055, 1054, 1053, 1057, 1056, 1059, 1059, 1058, 1057, 1061, 1060, 1063, 1063, 1062, 1061, 1065, 1064, 1067, 1067, 1066, 1065, 1069, 1068, 1071, 1071, 1070, 1069, 1073, 1072, 1075, 1075, 1074, 1073, 1077, 1076, 1079, 1079, 1078, 1077, 1081, 1080, 1083, 1083, 1082, 1081, 1085, 1084, 1087, 1087, 1086, 1085, 1089, 1088, 1091, 1091, 1090, 1089, 1093, 1092, 1095, 1095, 1094, 1093, 1097, 1096, 1099, 1099, 1098, 1097, 1101, 1100, 1103, 1103, 1102, 1101, 1105, 1104, 1107, 1107, 1106, 1105, 1109, 1108, 1111, 1111, 1110, 1109, 1113, 1112, 1115, 1115, 1114, 1113, 1117, 1116, 1119, 1119, 1118, 1117, 1121, 1120, 1123, 1123, 1122, 1121, 1125, 1124, 1127, 1127, 1126, 1125, 1129, 1128, 1131, 1131, 1130, 1129, 1133, 1132, 1135, 1135, 1134, 1133, 1137, 1136, 1139, 1139, 1138, 1137, 1141, 1140, 1143, 1143, 1142, 1141, 1145, 1144, 1147, 1147, 1146, 1145, 1149, 1148, 1151, 1151, 1150, 1149, 1153, 1152, 1155, 1155, 1154, 1153, 1157, 1156, 1159, 1159, 1158, 1157, 1161, 1160, 1163, 1163, 1162, 1161, 1165, 1164, 1167, 1167, 1166, 1165, 1169, 1168, 1171, 1171, 1170, 1169, 1173, 1172, 1175, 1175, 1174, 1173, 1177, 1176, 1179, 1179, 1178, 1177, 1181, 1180, 1183, 1183, 1182, 1181, 1185, 1184, 1187, 1187, 1186, 1185, 1189, 1188, 1191, 1191, 1190, 1189, 1193, 1192, 1195, 1195, 1194, 1193, 1197, 1196, 1199, 1199, 1198, 1197, 1201, 1200, 1203, 1203, 1202, 1201, 1205, 1204, 1207, 1207, 1206, 1205, 1209, 1208, 1211, 1211, 1210, 1209, 1213, 1212, 1215, 1215, 1214, 1213, 1217, 1216, 1219, 1219, 1218, 1217, 1221, 1220, 1223, 1223, 1222, 1221, 1225, 1224, 1227, 1227, 1226, 1225, 1229, 1228, 1231, 1231, 1230, 1229, 1233, 1232, 1235, 1235, 1234, 1233, 1237, 1236, 1239, 1239, 1238, 1237, 1241, 1240, 1243, 1243, 1242, 1241, 1245, 1244, 1247, 1247, 1246, 1245, 1249, 1248, 1251, 1251, 1250, 1249, 1253, 1252, 1255, 1255, 1254, 1253, 1257, 1256, 1259, 1259, 1258, 1257, 1261, 1260, 1263, 1263, 1262, 1261, 1265, 1264, 1267, 1267, 1266, 1265, 1269, 1268, 1271, 1271, 1270, 1269, 1273, 1272, 1275, 1275, 1274, 1273, 1277, 1276, 1279, 1279, 1278, 1277, 1281, 1280, 1283, 1283, 1282, 1281, 1285, 1284, 1287, 1287, 1286, 1285, 1289, 1288, 1291, 1291, 1290, 1289, 1293, 1292, 1295, 1295, 1294, 1293, 1297, 1296, 1299, 1299, 1298, 1297, 1301, 1300, 1303, 1303, 1302, 1301, 1305, 1304, 1307, 1307, 1306, 1305, 1309, 1308, 1311, 1311, 1310, 1309, 1313, 1312, 1315, 1315, 1314, 1313, 1317, 1316, 1319, 1319, 1318, 1317, 1321, 1320, 1323, 1323, 1322, 1321, 1325, 1324, 1327, 1327, 1326, 1325, 1329, 1328, 1331, 1331, 1330, 1329, 1333, 1332, 1335, 1335, 1334, 1333, 1337, 1336, 1339, 1339, 1338, 1337, 1341, 1340, 1343, 1343, 1342, 1341, 1345, 1344, 1347, 1347, 1346, 1345, 1349, 1348, 1351, 1351, 1350, 1349, 1353, 1352, 1355, 1355, 1354, 1353, 1357, 1356, 1359, 1359, 1358, 1357, 1361, 1360, 1363, 1363, 1362, 1361, 1365, 1364, 1367, 1367, 1366, 1365, 1369, 1368, 1371, 1371, 1370, 1369, 1373, 1372, 1375, 1375, 1374, 1373, 1377, 1376, 1379, 1379, 1378, 1377, 1381, 1380, 1383, 1383, 1382, 1381, 1385, 1384, 1387, 1387, 1386, 1385, 1389, 1388, 1391, 1391, 1390, 1389, 1393, 1392, 1395, 1395, 1394, 1393, 1397, 1396, 1399, 1399, 1398, 1397, 1401, 1400, 1403, 1403, 1402, 1401, 1405, 1404, 1407, 1407, 1406, 1405, 1409, 1408, 1411, 1411, 1410, 1409, 1413, 1412, 1415, 1415, 1414, 1413, 1417, 1416, 1419, 1419, 1418, 1417, 1421, 1420, 1423, 1423, 1422, 1421, 1425, 1424, 1427, 1427, 1426, 1425, 1429, 1428, 1431, 1431, 1430, 1429, 1433, 1432, 1435, 1435, 1434, 1433, 1437, 1436, 1439, 1439, 1438, 1437, 1441, 1440, 1443, 1443, 1442, 1441, 1445, 1444, 1447, 1447, 1446, 1445, 1449, 1448, 1451, 1451, 1450, 1449, 1453, 1452, 1455, 1455, 1454, 1453, 1457, 1456, 1459, 1459, 1458, 1457, 1461, 1460, 1463, 1463, 1462, 1461, 1465, 1464, 1467, 1467, 1466, 1465, 1469, 1468, 1471, 1471, 1470, 1469, 1473, 1472, 1475, 1475, 1474, 1473, 1477, 1476, 1479, 1479, 1478, 1477, 1481, 1480, 1483, 1483, 1482, 1481, 1485, 1484, 1487, 1487, 1486, 1485, 1489, 1488, 1491, 1491, 1490, 1489, 1493, 1492, 1495, 1495, 1494, 1493, 1497, 1496, 1499, 1499, 1498, 1497, 1501, 1500, 1503, 1503, 1502, 1501, 1505, 1504, 1507, 1507, 1506, 1505, 1509, 1508, 1511, 1511, 1510, 1509, 1513, 1512, 1515, 1515, 1514, 1513, 1517, 1516, 1519, 1519, 1518, 1517, 1521, 1520, 1523, 1523, 1522, 1521, 1525, 1524, 1527, 1527, 1526, 1525, 1529, 1528, 1531, 1531, 1530, 1529, 1533, 1532, 1535, 1535, 1534, 1533, 1537, 1536, 1539, 1539, 1538, 1537, 1541, 1540, 1543, 1543, 1542, 1541, 1545, 1544, 1547, 1547, 1546, 1545, 1549, 1548, 1551, 1551, 1550, 1549, 1553, 1552, 1555, 1555, 1554, 1553, 1557, 1556, 1559, 1559, 1558, 1557, 1561, 1560, 1563, 1563, 1562, 1561, 1565, 1564, 1567, 1567, 1566, 1565, 1569, 1568, 1571, 1571, 1570, 1569, 1573, 1572, 1575, 1575, 1574, 1573, 1577, 1576, 1579, 1579, 1578, 1577, 1581, 1580, 1583, 1583, 1582, 1581, 1585, 1584, 1587, 1587, 1586, 1585, 1589, 1588, 1591, 1591, 1590, 1589, 1593, 1592, 1595, 1595, 1594, 1593, 1597, 1596, 1599, 1599, 1598, 1597, 1601, 1600, 1603, 1603, 1602, 1601, 1605, 1604, 1607, 1607, 1606, 1605, 1609, 1608, 1611, 1611, 1610, 1609, 1613, 1612, 1615, 1615, 1614, 1613, 1617, 1616, 1619, 1619, 1618, 1617, 1621, 1620, 1623, 1623, 1622, 1621, 1625, 1624, 1627, 1627, 1626, 1625, 1629, 1628, 1631, 1631, 1630, 1629, 1633, 1632, 1635, 1635, 1634, 1633, 1637, 1636, 1639, 1639, 1638, 1637, 1641, 1640, 1643, 1643, 1642, 1641, 1645, 1644, 1647, 1647, 1646, 1645, 1649, 1648, 1651, 1651, 1650, 1649, 1653, 1652, 1655, 1655, 1654, 1653, 1657, 1656, 1659, 1659, 1658, 1657, 1661, 1660, 1663, 1663, 1662, 1661, 1665, 1664, 1667, 1667, 1666, 1665, 1669, 1668, 1671, 1671, 1670, 1669, 1673, 1672, 1675, 1675, 1674, 1673, 1677, 1676, 1679, 1679, 1678, 1677, 1681, 1680, 1683, 1683, 1682, 1681, 1685, 1684, 1687, 1687, 1686, 1685, 1689, 1688, 1691, 1691, 1690, 1689, 1693, 1692, 1695, 1695, 1694, 1693, 1697, 1696, 1699, 1699, 1698, 1697, 1701, 1700, 1703, 1703, 1702, 1701, 1705, 1704, 1707, 1707, 1706, 1705, 1709, 1708, 1711, 1711, 1710, 1709, 1713, 1712, 1715, 1715, 1714, 1713, 1717, 1716, 1719, 1719, 1718, 1717, 1721, 1720, 1723, 1723, 1722, 1721, 1725, 1724, 1727, 1727, 1726, 1725, 1729, 1728, 1731, 1731, 1730, 1729, 1733, 1732, 1735, 1735, 1734, 1733, 1737, 1736, 1739, 1739, 1738, 1737, 1741, 1740, 1743, 1743, 1742, 1741, 1745, 1744, 1747, 1747, 1746, 1745, 1749, 1748, 1751, 1751, 1750, 1749, 1753, 1752, 1755, 1755, 1754, 1753, 1757, 1756, 1759, 1759, 1758, 1757, 1761, 1760, 1763, 1763, 1762, 1761, 1765, 1764, 1767, 1767, 1766, 1765, 1769, 1768, 1771, 1771, 1770, 1769, 1773, 1772, 1775, 1775, 1774, 1773, 1777, 1776, 1779, 1779, 1778, 1777, 1781, 1780, 1783, 1783, 1782, 1781, 1785, 1784, 1787, 1787, 1786, 1785, 1789, 1788, 1791, 1791, 1790, 1789, 1793, 1792, 1795, 1795, 1794, 1793, 1797, 1796, 1799, 1799, 1798, 1797, 1801, 1800, 1803, 1803, 1802, 1801, 1805, 1804, 1807, 1807, 1806, 1805, 1809, 1808, 1811, 1811, 1810, 1809, 1813, 1812, 1815, 1815, 1814, 1813, 1817, 1816, 1819, 1819, 1818, 1817, 1821, 1820, 1823, 1823, 1822, 1821, 1825, 1824, 1827, 1827, 1826, 1825, 1829, 1828, 1831, 1831, 1830, 1829, 1833, 1832, 1835, 1835, 1834, 1833, 1837, 1836, 1839, 1839, 1838, 1837, 1841, 1840, 1843, 1843, 1842, 1841, 1845, 1844, 1847, 1847, 1846, 1845, 1849, 1848, 1851, 1851, 1850, 1849, 1853, 1852, 1855, 1855, 1854, 1853, 1857, 1856, 1859, 1859, 1858, 1857, 1861, 1860, 1863, 1863, 1862, 1861, 1865, 1864, 1867, 1867, 1866, 1865, 1869, 1868, 1871, 1871, 1870, 1869, 1873, 1872, 1875, 1875, 1874, 1873, 1877, 1876, 1879, 1879, 1878, 1877, 1881, 1880, 1883, 1883, 1882, 1881, 1885, 1884, 1887, 1887, 1886, 1885, 1889, 1888, 1891, 1891, 1890, 1889, 1893, 1892, 1895, 1895, 1894, 1893, 1897, 1896, 1899, 1899, 1898, 1897, 1901, 1900, 1903, 1903, 1902, 1901, 1905, 1904, 1907, 1907, 1906, 1905, 1909, 1908, 1911, 1911, 1910, 1909, 1913, 1912, 1915, 1915, 1914, 1913, 1917, 1916, 1919, 1919, 1918, 1917, 1921, 1920, 1923, 1923, 1922, 1921, 1925, 1924, 1927, 1927, 1926, 1925, 1929, 1928, 1931, 1931, 1930, 1929, 1933, 1932, 1935, 1935, 1934, 1933, 1937, 1936, 1939, 1939, 1938, 1937, 1941, 1940, 1943, 1943, 1942, 1941, 1945, 1944, 1947, 1947, 1946, 1945, 1949, 1948, 1951, 1951, 1950, 1949, 1953, 1952, 1955, 1955, 1954, 1953, 1957, 1956, 1959, 1959, 1958, 1957, 1961, 1960, 1963, 1963, 1962, 1961, 1965, 1964, 1967, 1967, 1966, 1965, 1969, 1968, 1971, 1971, 1970, 1969, 1973, 1972, 1975, 1975, 1974, 1973, 1977, 1976, 1979, 1979, 1978, 1977, 1981, 1980, 1983, 1983, 1982, 1981, 1985, 1984, 1987, 1987, 1986, 1985, 1989, 1988, 1991, 1991, 1990, 1989, 1993, 1992, 1995, 1995, 1994, 1993, 1997, 1996, 1999, 1999, 1998, 1997, 2001, 2000, 2003, 2003, 2002, 2001, 2005, 2004, 2007, 2007, 2006, 2005, 2009, 2008, 2011, 2011, 2010, 2009, 2013, 2012, 2015, 2015, 2014, 2013, 2017, 2016, 2019, 2019, 2018, 2017, 2021, 2020, 2023, 2023, 2022, 2021, 2025, 2024, 2027, 2027, 2026, 2025, 2029, 2028, 2031, 2031, 2030, 2029, 2033, 2032, 2035, 2035, 2034, 2033, 2037, 2036, 2039, 2039, 2038, 2037, 2041, 2040, 2043, 2043, 2042, 2041, 2045, 2044, 2047, 2047, 2046, 2045 }; template T align(T value, int bytes) { return (value + bytes - 1) & ~T(bytes - 1); } class IndexBuffer { public: IndexBuffer(); ~IndexBuffer(); void accomodate(int count); void bind(); private: GLuint m_buffer; size_t m_size; int m_count; }; IndexBuffer::IndexBuffer() { // The maximum number of quads we can render with 16 bit indices is 16,384. // But we start with 512 and grow the buffer as needed. m_size = sizeof(indices); m_count = m_size / (6 * sizeof(uint16_t)); glGenBuffers(1, &m_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); } IndexBuffer::~IndexBuffer() { glDeleteBuffers(1, &m_buffer); } void IndexBuffer::accomodate(int count) { // Check if we need to grow the buffer. if (count <= m_count) return; count = align(count, 128); size_t size = 6 * sizeof(uint16_t) * count; // Create a new buffer object GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, GL_STATIC_DRAW); // Use the GPU to copy the data from the old object to the new object, glBindBuffer(GL_COPY_READ_BUFFER, m_buffer); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, m_size); glDeleteBuffers(1, &m_buffer); glFlush(); // Needed to work around what appears to be a CP DMA issue in r600g // Map the new object and fill in the uninitialized section const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT; uint16_t *map = (uint16_t *) glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, m_size, size - m_size, access); const uint16_t index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = m_count; i < count; i++) { for (int j = 0; j < 6; j++) *(map++) = i * 4 + index[j]; } glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); m_buffer = buffer; m_count = count; m_size = size; } void IndexBuffer::bind() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); } // ------------------------------------------------------------------ class BitRef { public: BitRef(uint32_t &bitfield, int bit) : m_bitfield(bitfield), m_mask(1 << bit) {} void operator = (bool val) { if (val) m_bitfield |= m_mask; else m_bitfield &= ~m_mask; } operator bool () const { return m_bitfield & m_mask; } private: uint32_t &m_bitfield; int const m_mask; }; // ------------------------------------------------------------------ class Bitfield { public: Bitfield() : m_bitfield(0) {} Bitfield(uint32_t bits) : m_bitfield(bits) {} void set(int i) { m_bitfield |= (1 << i); } void clear(int i) { m_bitfield &= ~(1 << i); } BitRef operator [] (int i) { return BitRef(m_bitfield, i); } operator uint32_t () const { return m_bitfield; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ class BitfieldIterator { public: BitfieldIterator(uint32_t bitfield) : m_bitfield(bitfield) {} bool hasNext() const { return m_bitfield != 0; } int next() { const int bit = ffs(m_bitfield) - 1; m_bitfield ^= (1 << bit); return bit; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ struct VertexAttrib { int size; GLenum type; int offset; }; // ------------------------------------------------------------------ struct BufferFence { GLsync sync; intptr_t nextEnd; bool signaled() const { GLint value; glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value); return value == GL_SIGNALED; } }; static void deleteAll(std::deque &fences) { for (const BufferFence &fence : fences) glDeleteSync(fence.sync); fences.clear(); } // ------------------------------------------------------------------ template struct FrameSizesArray { public: FrameSizesArray() { m_array.fill(0); } void push(size_t size) { m_array[m_index] = size; m_index = (m_index + 1) % Count; } size_t average() const { size_t sum = 0; for (size_t size : m_array) sum += size; return sum / Count; } private: std::array m_array; int m_index = 0; }; //********************************* // GLVertexBufferPrivate //********************************* class GLVertexBufferPrivate { public: GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint) : vertexCount(0) , persistent(false) , useColor(false) , color(0, 0, 0, 255) , bufferSize(0) , bufferEnd(0) , mappedSize(0) , frameSize(0) , nextOffset(0) , baseAddress(0) , map(nullptr) { glGenBuffers(1, &buffer); switch(usageHint) { case GLVertexBuffer::Dynamic: usage = GL_DYNAMIC_DRAW; break; case GLVertexBuffer::Static: usage = GL_STATIC_DRAW; break; default: usage = GL_STREAM_DRAW; break; } } ~GLVertexBufferPrivate() { deleteAll(fences); if (buffer != 0) { glDeleteBuffers(1, &buffer); map = nullptr; } } void interleaveArrays(float *array, int dim, const float *vertices, const float *texcoords, int count); void bindArrays(); void unbindArrays(); void reallocateBuffer(size_t size); GLvoid *mapNextFreeRange(size_t size); void reallocatePersistentBuffer(size_t size); bool awaitFence(intptr_t offset); GLvoid *getIdleRange(size_t size); GLuint buffer; GLenum usage; int stride; int vertexCount; static GLVertexBuffer *streamingBuffer; static bool haveBufferStorage; static bool haveSyncFences; static bool hasMapBufferRange; static bool supportsIndexedQuads; QByteArray dataStore; bool persistent; bool useColor; QVector4D color; size_t bufferSize; intptr_t bufferEnd; size_t mappedSize; size_t frameSize; intptr_t nextOffset; intptr_t baseAddress; uint8_t *map; std::deque fences; FrameSizesArray<4> frameSizes; VertexAttrib attrib[VertexAttributeCount]; Bitfield enabledArrays; static IndexBuffer *s_indexBuffer; }; bool GLVertexBufferPrivate::hasMapBufferRange = false; bool GLVertexBufferPrivate::supportsIndexedQuads = false; GLVertexBuffer *GLVertexBufferPrivate::streamingBuffer = nullptr; bool GLVertexBufferPrivate::haveBufferStorage = false; bool GLVertexBufferPrivate::haveSyncFences = false; IndexBuffer *GLVertexBufferPrivate::s_indexBuffer = nullptr; void GLVertexBufferPrivate::interleaveArrays(float *dst, int dim, const float *vertices, const float *texcoords, int count) { if (!texcoords) { memcpy((void *) dst, vertices, dim * sizeof(float) * count); return; } switch (dim) { case 2: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; case 3: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; default: for (int i = 0; i < count; i++) { for (int j = 0; j < dim; j++) *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } } } void GLVertexBufferPrivate::bindArrays() { if (useColor) { GLShader *shader = ShaderManager::instance()->getBoundShader(); shader->setUniform(GLShader::Color, color); } glBindBuffer(GL_ARRAY_BUFFER, buffer); BitfieldIterator it(enabledArrays); while (it.hasNext()) { const int index = it.next(); glVertexAttribPointer(index, attrib[index].size, attrib[index].type, GL_FALSE, stride, (const GLvoid *) (baseAddress + attrib[index].offset)); glEnableVertexAttribArray(index); } } void GLVertexBufferPrivate::unbindArrays() { BitfieldIterator it(enabledArrays); while (it.hasNext()) glDisableVertexAttribArray(it.next()); } void GLVertexBufferPrivate::reallocatePersistentBuffer(size_t size) { if (buffer != 0) { // This also unmaps and unbinds the buffer glDeleteBuffers(1, &buffer); buffer = 0; deleteAll(fences); } if (buffer == 0) glGenBuffers(1, &buffer); // Round the size up to 64 kb size_t minSize = qMax(frameSizes.average() * 3, 128 * 1024); bufferSize = align(qMax(size, minSize), 64 * 1024); const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT; const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access); map = (uint8_t *) glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access); nextOffset = 0; bufferEnd = bufferSize; } bool GLVertexBufferPrivate::awaitFence(intptr_t end) { // Skip fences until we reach the end offset while (!fences.empty() && fences.front().nextEnd < end) { glDeleteSync(fences.front().sync); fences.pop_front(); } assert(!fences.empty()); // Wait on the next fence const BufferFence &fence = fences.front(); if (!fence.signaled()) { qCDebug(LIBKWINGLUTILS) << "Stalling on VBO fence"; const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000); if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) { qCCritical(LIBKWINGLUTILS) << "Wait failed"; return false; } } glDeleteSync(fence.sync); // Update the end pointer bufferEnd = fence.nextEnd; fences.pop_front(); return true; } GLvoid *GLVertexBufferPrivate::getIdleRange(size_t size) { if (unlikely(size > bufferSize)) reallocatePersistentBuffer(size * 2); // Handle wrap-around if (unlikely(nextOffset + size > bufferSize)) { nextOffset = 0; bufferEnd -= bufferSize; for (BufferFence &fence : fences) fence.nextEnd -= bufferSize; // Emit a fence now BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = bufferSize; fences.emplace_back(fence); } if (unlikely(nextOffset + intptr_t(size) > bufferEnd)) { if (!awaitFence(nextOffset + size)) return nullptr; } return map + nextOffset; } void GLVertexBufferPrivate::reallocateBuffer(size_t size) { // Round the size up to 4 Kb for streaming/dynamic buffers. const size_t minSize = 32768; // Minimum size for streaming buffers const size_t alloc = usage != GL_STATIC_DRAW ? align(qMax(size, minSize), 4096) : size; glBufferData(GL_ARRAY_BUFFER, alloc, 0, usage); bufferSize = alloc; } GLvoid *GLVertexBufferPrivate::mapNextFreeRange(size_t size) { GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT; if ((nextOffset + size) > bufferSize) { // Reallocate the data store if it's too small. if (size > bufferSize) { reallocateBuffer(size); } else { access |= GL_MAP_INVALIDATE_BUFFER_BIT; access ^= GL_MAP_UNSYNCHRONIZED_BIT; } nextOffset = 0; } return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access); } //********************************* // GLVertexBuffer //********************************* QRect GLVertexBuffer::s_virtualScreenGeometry; qreal GLVertexBuffer::s_virtualScreenScale; GLVertexBuffer::GLVertexBuffer(UsageHint hint) : d(new GLVertexBufferPrivate(hint)) { } GLVertexBuffer::~GLVertexBuffer() { delete d; } void GLVertexBuffer::setData(const void *data, size_t size) { GLvoid *ptr = map(size); memcpy(ptr, data, size); unmap(); } void GLVertexBuffer::setData(int vertexCount, int dim, const float* vertices, const float* texcoords) { const GLVertexAttrib layout[] = { { VA_Position, dim, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, int(dim * sizeof(float)) } }; int stride = (texcoords ? dim + 2 : dim) * sizeof(float); int attribCount = texcoords ? 2 : 1; setAttribLayout(layout, attribCount, stride); setVertexCount(vertexCount); GLvoid *ptr = map(vertexCount * stride); d->interleaveArrays((float *) ptr, dim, vertices, texcoords, vertexCount); unmap(); } GLvoid *GLVertexBuffer::map(size_t size) { d->mappedSize = size; d->frameSize += size; if (d->persistent) return d->getIdleRange(size); glBindBuffer(GL_ARRAY_BUFFER, d->buffer); bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) return (GLvoid *) d->mapNextFreeRange(size); // If we can't map the buffer we allocate local memory to hold the // buffer data and return a pointer to it. The data will be submitted // to the actual buffer object when the user calls unmap(). if (size_t(d->dataStore.size()) < size) d->dataStore.resize(size); return (GLvoid *) d->dataStore.data(); } void GLVertexBuffer::unmap() { if (d->persistent) { d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE d->mappedSize = 0; return; } bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) { glUnmapBuffer(GL_ARRAY_BUFFER); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { // Upload the data from local memory to the buffer object if (preferBufferSubData) { if ((d->nextOffset + d->mappedSize) > d->bufferSize) { d->reallocateBuffer(d->mappedSize); d->nextOffset = 0; } glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData()); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage); d->baseAddress = 0; } // Free the local memory buffer if it's unlikely to be used again if (d->usage == GL_STATIC_DRAW) d->dataStore = QByteArray(); } d->mappedSize = 0; } void GLVertexBuffer::setVertexCount(int count) { d->vertexCount = count; } void GLVertexBuffer::setAttribLayout(const GLVertexAttrib *attribs, int count, int stride) { // Start by disabling all arrays d->enabledArrays = 0; for (int i = 0; i < count; i++) { const int index = attribs[i].index; assert(index >= 0 && index < VertexAttributeCount); assert(!d->enabledArrays[index]); d->attrib[index].size = attribs[i].size; d->attrib[index].type = attribs[i].type; d->attrib[index].offset = attribs[i].relativeOffset; d->enabledArrays[index] = true; } d->stride = stride; } void GLVertexBuffer::render(GLenum primitiveMode) { render(infiniteRegion(), primitiveMode, false); } void GLVertexBuffer::render(const QRegion& region, GLenum primitiveMode, bool hardwareClipping) { d->bindArrays(); draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping); d->unbindArrays(); } void GLVertexBuffer::bindArrays() { d->bindArrays(); } void GLVertexBuffer::unbindArrays() { d->unbindArrays(); } void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count) { draw(infiniteRegion(), primitiveMode, first, count, false); } void GLVertexBuffer::draw(const QRegion ®ion, GLenum primitiveMode, int first, int count, bool hardwareClipping) { if (primitiveMode == GL_QUADS) { IndexBuffer *&indexBuffer = GLVertexBufferPrivate::s_indexBuffer; if (!indexBuffer) indexBuffer = new IndexBuffer; indexBuffer->bind(); indexBuffer->accomodate(count / 4); count = count * 6 / 4; if (!hardwareClipping) { glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } else { // Clip using scissoring for (const QRect &r : region) { glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, r.width() * s_virtualScreenScale, r.height() * s_virtualScreenScale); glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } } return; } if (!hardwareClipping) { glDrawArrays(primitiveMode, first, count); } else { // Clip using scissoring for (const QRect &r : region) { glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, r.width() * s_virtualScreenScale, r.height() * s_virtualScreenScale); glDrawArrays(primitiveMode, first, count); } } } bool GLVertexBuffer::supportsIndexedQuads() { return GLVertexBufferPrivate::supportsIndexedQuads; } 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 = QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } void GLVertexBuffer::reset() { d->useColor = false; d->color = QVector4D(0, 0, 0, 1); d->vertexCount = 0; } void GLVertexBuffer::endOfFrame() { if (!d->persistent) return; // Emit a fence if we have uploaded data if (d->frameSize > 0) { d->frameSizes.push(d->frameSize); d->frameSize = 0; // Force the buffer to be reallocated at the beginning of the next frame // if the average frame size is greater than half the size of the buffer if (unlikely(d->frameSizes.average() > d->bufferSize / 2)) { deleteAll(d->fences); glDeleteBuffers(1, &d->buffer); d->buffer = 0; d->bufferSize = 0; d->nextOffset = 0; d->map = nullptr; } else { BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = d->nextOffset + d->bufferSize; d->fences.emplace_back(fence); } } } void GLVertexBuffer::framePosted() { if (!d->persistent) return; // Remove finished fences from the list and update the bufferEnd offset while (d->fences.size() > 1 && d->fences.front().signaled()) { const BufferFence &fence = d->fences.front(); glDeleteSync(fence.sync); d->bufferEnd = fence.nextEnd; d->fences.pop_front(); } } void GLVertexBuffer::initStatic() { if (GLPlatform::instance()->isGLES()) { bool haveBaseVertex = hasGLExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 0); bool haveMapBufferRange = hasGLExtension(QByteArrayLiteral("GL_EXT_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLExtension("GL_EXT_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 0); } else { bool haveBaseVertex = hasGLVersion(3, 2) || hasGLExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 1) || hasGLExtension(QByteArrayLiteral("GL_ARB_copy_buffer")); bool haveMapBufferRange = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLVersion(4, 4) || hasGLExtension("GL_ARB_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); } GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::streamingBuffer = new GLVertexBuffer(GLVertexBuffer::Stream); if (GLVertexBufferPrivate::haveBufferStorage && GLVertexBufferPrivate::haveSyncFences) { if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) { GLVertexBufferPrivate::streamingBuffer->d->persistent = true; } } } void GLVertexBuffer::cleanup() { delete GLVertexBufferPrivate::s_indexBuffer; GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::hasMapBufferRange = false; GLVertexBufferPrivate::supportsIndexedQuads = false; delete GLVertexBufferPrivate::streamingBuffer; GLVertexBufferPrivate::streamingBuffer = nullptr; } GLVertexBuffer *GLVertexBuffer::streamingBuffer() { return GLVertexBufferPrivate::streamingBuffer; } } // namespace diff --git a/libkwineffects/kwinglutils.h b/libkwineffects/kwinglutils.h index 02db01ba1..b0045115b 100644 --- a/libkwineffects/kwinglutils.h +++ b/libkwineffects/kwinglutils.h @@ -1,801 +1,825 @@ /******************************************************************** 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 // kwin #include #include "kwinglutils_funcs.h" #include "kwingltexture.h" // Qt #include #include /** @addtogroup kwineffects */ /** @{ */ class QVector2D; class QVector3D; class QVector4D; class QMatrix4x4; template< class K, class V > class QHash; namespace KWin { class GLVertexBuffer; class GLVertexBufferPrivate; // 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 typedef void (*resolveFuncPtr)(); void KWINGLUTILS_EXPORT initGL(std::function resolveFunction); // Cleans up all resources hold by the GL Context void KWINGLUTILS_EXPORT cleanupGL(); bool KWINGLUTILS_EXPORT hasGLVersion(int major, int minor, int release = 0); // use for both OpenGL and GLX extensions bool KWINGLUTILS_EXPORT hasGLExtension(const QByteArray &extension); // detect OpenGL error (add to various places in code to pinpoint the place) bool KWINGLUTILS_EXPORT checkGLError(const char* txt); QList KWINGLUTILS_EXPORT openGLExtensions(); class KWINGLUTILS_EXPORT GLShader { public: enum Flags { NoFlags = 0, ExplicitLinking = (1 << 0) }; GLShader(const QString &vertexfile, const QString &fragmentfile, unsigned int flags = NoFlags); ~GLShader(); bool isValid() const { return mValid; } void bindAttributeLocation(const char *name, int index); void bindFragDataLocation(const char *name, int index); bool link(); 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, ModelViewProjectionMatrix, WindowTransformation, ScreenTransformation, MatrixCount }; enum Vec2Uniform { Offset, Vec2UniformCount }; enum Vec4Uniform { ModulationConstant, Vec4UniformCount }; enum FloatUniform { Saturation, FloatUniformCount }; enum IntUniform { AlphaToOne, ///< @deprecated no longer used IntUniformCount }; enum ColorUniform { Color, ColorUniformCount }; 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); bool setUniform(ColorUniform uniform, const QVector4D &value); bool setUniform(ColorUniform uniform, const QColor &value); protected: GLShader(unsigned int flags = NoFlags); bool loadFromFiles(const QString& vertexfile, const QString& fragmentfile); bool load(const QByteArray &vertexSource, const QByteArray &fragmentSource); const QByteArray prepareSource(GLenum shaderType, const QByteArray &sourceCode) const; 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; bool mExplicitLinking:1; int mMatrixLocation[MatrixCount]; int mVec2Location[Vec2UniformCount]; int mVec4Location[Vec4UniformCount]; int mFloatLocation[FloatUniformCount]; int mIntLocation[IntUniformCount]; int mColorLocation[ColorUniformCount]; friend class ShaderManager; }; enum class ShaderTrait { MapTexture = (1 << 0), UniformColor = (1 << 1), Modulate = (1 << 2), AdjustSaturation = (1 << 3), }; Q_DECLARE_FLAGS(ShaderTraits, ShaderTrait) /** * @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 KWINGLUTILS_EXPORT ShaderManager { public: /** * Returns a shader with the given traits, creating it if necessary. */ GLShader *shader(ShaderTraits traits); /** * @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; /** * 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; /** * Pushes the current shader onto the stack and binds a shader * with the given traits. */ GLShader *pushShader(ShaderTraits traits); /** * 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 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); /** * Creates a custom shader with the given @p traits and custom @p vertexSource and or @p fragmentSource. * If the @p vertexSource is empty a vertex shader with the given @p traits is generated. * If it is not empty the @p vertexSource is used as the source for the vertex shader. * * The same applies for argument @p fragmentSource just for the fragment shader. * * So if both @p vertesSource and @p fragmentSource are provided the @p traits are ignored. * If neither are provided a new shader following the @p traits is generated. * * @param traits The shader traits for generating the shader * @param vertesSource optional vertex shader source code to be used instead of shader traits * @param fragmentSource optional fragment shader source code to be used instead of shader traits * @return new generated shader * @since 5.6 **/ GLShader *generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource = QByteArray(), const QByteArray &fragmentSource = QByteArray()); /** * Creates a custom shader with the given @p traits and custom @p vertexFile and or @p fragmentFile. * The file names specified in @p vertexFile and @p fragmentFile are relative paths to the shaders * resource file shipped together with KWin. This means this method can only be used for built-in * effects, for 3rd party effects @link {generateCustomShader} should be used. * * If the @p vertexFile is empty a vertex shader with the given @p traits is generated. * If it is not empty the @p vertexFile is used as the source for the vertex shader. * * The same applies for argument @p fragmentFile just for the fragment shader. * * So if both @p vertexFile and @p fragmentFile are provided the @p traits are ignored. * If neither are provided a new shader following the @p traits is generated. * * @param traits The shader traits for generating the shader * @param vertexFile optional vertex shader source code to be used instead of shader traits * @param fragmentFile optional fragment shader source code to be used instead of shader traits * @return new generated shader * @see generateCustomShader * @since 5.6 **/ GLShader *generateShaderFromResources(ShaderTraits traits, const QString &vertexFile = QString(), const QString &fragmentFile = QString()); /** * Compiles and tests the dynamically generated shaders. * Returns true if successful and false otherwise. */ bool selfTest(); /** * @return a pointer to the ShaderManager instance **/ static ShaderManager *instance(); /** * @internal **/ static void cleanup(); private: ShaderManager(); ~ShaderManager(); void bindFragDataLocations(GLShader *shader); void bindAttributeLocations(GLShader *shader) const; QByteArray generateVertexSource(ShaderTraits traits) const; QByteArray generateFragmentSource(ShaderTraits traits) const; GLShader *generateShader(ShaderTraits traits); QStack m_boundShaders; QHash m_shaderHash; bool m_debug; QString m_resourcePath; static ShaderManager *s_shaderManager; }; /** * An helper class to push a Shader on to ShaderManager's stack and ensuring that the Shader * gets popped again from the stack automatically once the object goes out of life. * * How to use: * @code * { * GLShader *myCustomShaderIWantToPush; * ShaderBinder binder(myCustomShaderIWantToPush); * // do stuff with the shader being pushed on the stack * } * // here the Shader is automatically popped as helper does no longer exist. * @endcode * * @since 4.10 **/ class KWINGLUTILS_EXPORT ShaderBinder { public: /** * @brief Pushes the given @p shader to the ShaderManager's stack. * * @param shader The Shader to push on the stack * @see ShaderManager::pushShader **/ explicit ShaderBinder(GLShader *shader); /** * @brief Pushes the Shader with the given @p traits to the ShaderManager's stack. * * @param traits The traits describing the shader * @see ShaderManager::pushShader * @since 5.6 **/ explicit ShaderBinder(ShaderTraits traits); ~ShaderBinder(); /** * @return The Shader pushed to the Stack. **/ GLShader *shader(); private: GLShader *m_shader; }; inline ShaderBinder::ShaderBinder(GLShader *shader) : m_shader(shader) { ShaderManager::instance()->pushShader(shader); } inline ShaderBinder::ShaderBinder(ShaderTraits traits) : m_shader(nullptr) { m_shader = ShaderManager::instance()->pushShader(traits); } inline ShaderBinder::~ShaderBinder() { ShaderManager::instance()->popShader(); } inline GLShader* ShaderBinder::shader() { return m_shader; } /** * @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 KWINGLUTILS_EXPORT GLRenderTarget { public: + /** + * Constructs a GLRenderTarget + * @since 5.13 + **/ + explicit GLRenderTarget(); + /** * Constructs a GLRenderTarget * @param color texture where the scene will be rendered onto **/ explicit 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); + /** + * Detaches the texture that is currently attached to this framebuffer object. + * @since 5.13 + **/ + void detachTexture(); + bool valid() const { return mValid; } + void setTextureDirty() { + mTexture.setDirty(); + } + static void initStatic(); static bool supported() { return sSupported; } + + /** + * Pushes the render target stack of the input parameter in reverse order. + * @param targets The stack of GLRenderTargets + * @since 5.13 + **/ + static void pushRenderTargets(QStack targets); + 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); /** * Sets the virtual screen size to @p s. * @since 5.2 **/ static void setVirtualScreenSize(const QSize &s) { s_virtualScreenSize = s; } /** * Sets the virtual screen geometry to @p g. * This is the geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering geometries use. * @see virtualScreenGeometry * @since 5.9 **/ static void setVirtualScreenGeometry(const QRect &g) { s_virtualScreenGeometry = g; } /** * The geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering system uses. * @see setVirtualScreenGeometry * @since 5.9 **/ static QRect virtualScreenGeometry() { return s_virtualScreenGeometry; } /** * The scale of the OpenGL window currently being rendered to * * @returns the ratio between the virtual geometry space the rendering * system uses and the target * @since 5.10 */ static void setVirtualScreenScale(qreal scale) { s_virtualScreenScale = scale; } static qreal virtualScreenScale() { return s_virtualScreenScale; } protected: void initFBO(); private: friend void KWin::cleanupGL(); static void cleanup(); static bool sSupported; static bool s_blitSupported; static QStack s_renderTargets; static QSize s_virtualScreenSize; static QRect s_virtualScreenGeometry; static qreal s_virtualScreenScale; static GLint s_virtualScreenViewport[4]; GLTexture mTexture; bool mValid; GLuint mFramebuffer; }; enum VertexAttributeType { VA_Position = 0, VA_TexCoord = 1, VertexAttributeCount = 2 }; /** * Describes the format of a vertex attribute stored in a buffer object. * * The attribute format consists of the attribute index, the number of * vector components, the data type, and the offset of the first element * relative to the start of the vertex data. */ struct GLVertexAttrib { int index; /** The attribute index */ int size; /** The number of components [1..4] */ GLenum type; /** The type (e.g. GL_FLOAT) */ int relativeOffset; /** The relative offset of the attribute */ }; /** * @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 KWINGLUTILS_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 }; explicit GLVertexBuffer(UsageHint hint); ~GLVertexBuffer(); /** * Specifies how interleaved vertex attributes are laid out in * the buffer object. * * Note that the attributes and the stride should be 32 bit aligned * or a performance penalty may be incurred. * * For some hardware the optimal stride is a multiple of 32 bytes. * * Example: * * struct Vertex { * QVector3D position; * QVector2D texcoord; * }; * * const GLVertexAttrib attribs[] = { * { VA_Position, 3, GL_FLOAT, offsetof(Vertex, position) }, * { VA_TexCoord, 2, GL_FLOAT, offsetof(Vertex, texcoord) } * }; * * Vertex vertices[6]; * vbo->setAttribLayout(attribs, 2, sizeof(Vertex)); * vbo->setData(vertices, sizeof(vertices)); */ void setAttribLayout(const GLVertexAttrib *attribs, int count, int stride); /** * Uploads data into the buffer object's data store. */ void setData(const void *data, size_t sizeInBytes); /** * Sets the number of vertices that will be drawn by the render() method. */ void setVertexCount(int count); /** * 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); /** * Maps an unused range of the data store into the client's address space. * * The data store will be reallocated if it is smaller than the given size. * * The buffer object is mapped for writing, not reading. Attempts to read from * the mapped buffer range may result in system errors, including program * termination. The data in the mapped region is undefined until it has been * written to. If subsequent GL calls access unwritten memory, the results are * undefined and system errors, including program termination, may occur. * * No GL calls that access the buffer object must be made while the buffer * object is mapped. The returned pointer must not be passed as a parameter * value to any GL function. * * It is assumed that the GL_ARRAY_BUFFER_BINDING will not be changed while * the buffer object is mapped. */ GLvoid *map(size_t size); /** * Flushes the mapped buffer range and unmaps the buffer. */ void unmap(); /** * Binds the vertex arrays to the context. */ void bindArrays(); /** * Disables the vertex arrays. */ void unbindArrays(); /** * Draws count vertices beginning with first. */ void draw(GLenum primitiveMode, int first, int count); /** * Draws count vertices beginning with first. */ void draw(const QRegion ®ion, GLenum primitiveMode, int first, int count, bool hardwareClipping = false); /** * 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 if @a hardwareClipping is true. * It's within the caller's responsibility to enable GL_SCISSOR_TEST. */ void render(const QRegion& region, GLenum primitiveMode, bool hardwareClipping = false); /** * 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(); /** * Notifies the vertex buffer that we are done painting the frame. * * @internal */ void endOfFrame(); /** * Notifies the vertex buffer that we have posted the frame. * * @internal */ void framePosted(); /** * @internal */ static void initStatic(); /** * @internal */ static void cleanup(); /** * Returns true if indexed quad mode is supported, and false otherwise. */ static bool supportsIndexedQuads(); /** * @return A shared VBO for streaming data * @since 4.7 **/ static GLVertexBuffer *streamingBuffer(); /** * Sets the virtual screen geometry to @p g. * This is the geometry of the OpenGL window currently being rendered to * in the virtual geometry space the rendering geometries use. * @since 5.9 **/ static void setVirtualScreenGeometry(const QRect &g) { s_virtualScreenGeometry = g; } /** * The scale of the OpenGL window currently being rendered to * * @returns the ratio between the virtual geometry space the rendering * system uses and the target * @since 5.11.3 */ static void setVirtualScreenScale(qreal s) { s_virtualScreenScale = s; } private: GLVertexBufferPrivate* const d; static QRect s_virtualScreenGeometry; static qreal s_virtualScreenScale; }; } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ShaderTraits) /** @} */ #endif