diff --git a/decorations/decorationrenderer.cpp b/decorations/decorationrenderer.cpp index c2f0e13e3..3efbceba3 100644 --- a/decorations/decorationrenderer.cpp +++ b/decorations/decorationrenderer.cpp @@ -1,85 +1,88 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 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 "decorationrenderer.h" #include "decoratedclient.h" #include "deleted.h" #include "abstract_client.h" +#include "screens.h" #include #include #include #include namespace KWin { namespace Decoration { Renderer::Renderer(DecoratedClientImpl *client) : QObject(client) , m_client(client) , m_imageSizesDirty(true) { - auto markImageSizesDirty = [this]{ m_imageSizesDirty = true; }; - if (kwinApp()->operationMode() != Application::OperationModeX11) { - connect(client->client(), &AbstractClient::screenChanged, this, markImageSizesDirty); - } + auto markImageSizesDirty = [this]{ + m_imageSizesDirty = true; + }; + connect(client->client(), &AbstractClient::screenScaleChanged, this, markImageSizesDirty); connect(client->decoration(), &KDecoration2::Decoration::bordersChanged, this, markImageSizesDirty); connect(client->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, markImageSizesDirty); connect(client->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, markImageSizesDirty); } Renderer::~Renderer() = default; void Renderer::schedule(const QRect &rect) { m_scheduled = m_scheduled.united(rect); emit renderScheduled(rect); } QRegion Renderer::getScheduled() { QRegion region = m_scheduled; m_scheduled = QRegion(); return region; } QImage Renderer::renderToImage(const QRect &geo) { Q_ASSERT(m_client); - QImage image(geo.width(), geo.height(), QImage::Format_ARGB32_Premultiplied); + auto dpr = client()->client()->screenScale(); + QImage image(geo.width() * dpr, geo.height() * dpr, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(dpr); image.fill(Qt::transparent); QPainter p(&image); p.setRenderHint(QPainter::Antialiasing); - p.setWindow(geo); + p.setWindow(QRect(geo.topLeft(), geo.size() * dpr)); p.setClipRect(geo); client()->decoration()->paint(&p, geo); return image; } void Renderer::reparent(Deleted *deleted) { setParent(deleted); m_client = nullptr; } } } diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index 134646fc4..ff865c828 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1,2460 +1,2464 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009, 2010, 2011 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. Explicit command stream synchronization based on the sample implementation by James Jones , Copyright © 2011 NVIDIA Corporation 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 "scene_opengl.h" #include "platform.h" #include "wayland_server.h" #include "platformsupport/scenes/opengl/texture.h" #include #include "utils.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "lanczosfilter.h" #include "main.h" #include "overlaywindow.h" #include "screens.h" #include "cursor.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // HACK: workaround for libepoxy < 1.3 #ifndef GL_GUILTY_CONTEXT_RESET #define GL_GUILTY_CONTEXT_RESET 0x8253 #endif #ifndef GL_INNOCENT_CONTEXT_RESET #define GL_INNOCENT_CONTEXT_RESET 0x8254 #endif #ifndef GL_UNKNOWN_CONTEXT_RESET #define GL_UNKNOWN_CONTEXT_RESET 0x8255 #endif namespace KWin { extern int currentRefreshRate(); /** * SyncObject represents a fence used to synchronize operations in * the kwin command stream with operations in the X command stream. */ class SyncObject { public: enum State { Ready, TriggerSent, Waiting, Done, Resetting }; SyncObject(); ~SyncObject(); State state() const { return m_state; } void trigger(); void wait(); bool finish(); void reset(); void finishResetting(); private: State m_state; GLsync m_sync; xcb_sync_fence_t m_fence; xcb_get_input_focus_cookie_t m_reset_cookie; }; SyncObject::SyncObject() { m_state = Ready; xcb_connection_t * const c = connection(); m_fence = xcb_generate_id(c); xcb_sync_create_fence(c, rootWindow(), m_fence, false); xcb_flush(c); m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); } SyncObject::~SyncObject() { // If glDeleteSync is called before the xcb fence is signalled // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) // deadlocks waiting for the fence to be signalled. // To avoid this, make sure the fence is signalled before // deleting the sync. if (m_state == Resetting || m_state == Ready){ trigger(); // The flush is necessary! // The trigger command needs to be sent to the X server. xcb_flush(connection()); } xcb_sync_destroy_fence(connection(), m_fence); glDeleteSync(m_sync); if (m_state == Resetting) xcb_discard_reply(connection(), m_reset_cookie.sequence); } void SyncObject::trigger() { assert(m_state == Ready || m_state == Resetting); // Finish resetting the fence if necessary if (m_state == Resetting) finishResetting(); xcb_sync_trigger_fence(connection(), m_fence); m_state = TriggerSent; } void SyncObject::wait() { if (m_state != TriggerSent) return; glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); m_state = Waiting; } bool SyncObject::finish() { if (m_state == Done) return true; // Note: It is possible that we never inserted a wait for the fence. // This can happen if we ended up not rendering the damaged // window because it is fully occluded. assert(m_state == TriggerSent || m_state == Waiting); // Check if the fence is signaled GLint value; glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); if (value != GL_SIGNALED) { qCDebug(KWIN_OPENGL) << "Waiting for X fence to finish"; // Wait for the fence to become signaled with a one second timeout const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); switch (result) { case GL_TIMEOUT_EXPIRED: qCWarning(KWIN_OPENGL) << "Timeout while waiting for X fence"; return false; case GL_WAIT_FAILED: qCWarning(KWIN_OPENGL) << "glClientWaitSync() failed"; return false; } } m_state = Done; return true; } void SyncObject::reset() { assert(m_state == Done); xcb_connection_t * const c = connection(); // Send the reset request along with a sync request. // We use the cookie to ensure that the server has processed the reset // request before we trigger the fence and call glWaitSync(). // Otherwise there is a race condition between the reset finishing and // the glWaitSync() call. xcb_sync_reset_fence(c, m_fence); m_reset_cookie = xcb_get_input_focus(c); xcb_flush(c); m_state = Resetting; } void SyncObject::finishResetting() { assert(m_state == Resetting); free(xcb_get_input_focus_reply(connection(), m_reset_cookie, nullptr)); m_state = Ready; } // ----------------------------------------------------------------------- /** * SyncManager manages a set of fences used for explicit synchronization * with the X command stream. */ class SyncManager { public: enum { MaxFences = 4 }; SyncManager(); ~SyncManager(); SyncObject *nextFence(); bool updateFences(); private: std::array m_fences; int m_next; }; SyncManager::SyncManager() : m_next(0) { } SyncManager::~SyncManager() { } SyncObject *SyncManager::nextFence() { SyncObject *fence = &m_fences[m_next]; m_next = (m_next + 1) % MaxFences; return fence; } bool SyncManager::updateFences() { for (int i = 0; i < qMin(2, MaxFences - 1); i++) { const int index = (m_next + i) % MaxFences; SyncObject &fence = m_fences[index]; switch (fence.state()) { case SyncObject::Ready: break; case SyncObject::TriggerSent: case SyncObject::Waiting: if (!fence.finish()) return false; fence.reset(); break; // Should not happen in practice since we always reset the fence // after finishing it case SyncObject::Done: fence.reset(); break; case SyncObject::Resetting: fence.finishResetting(); break; } } return true; } // ----------------------------------------------------------------------- /************************************************ * SceneOpenGL ***********************************************/ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) : Scene(parent) , init_ok(true) , m_backend(backend) , m_syncManager(nullptr) , m_currentFence(nullptr) { if (m_backend->isFailed()) { init_ok = false; return; } if (!viewportLimitsMatched(screens()->size())) return; // perform Scene specific checks GLPlatform *glPlatform = GLPlatform::instance(); if (!glPlatform->isGLES() && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_non_power_of_two")) && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_rectangle"))) { qCCritical(KWIN_OPENGL) << "GL_ARB_texture_non_power_of_two and GL_ARB_texture_rectangle missing"; init_ok = false; return; // error } if (glPlatform->isMesaDriver() && glPlatform->mesaVersion() < kVersionNumber(10, 0)) { qCCritical(KWIN_OPENGL) << "KWin requires at least Mesa 10.0 for OpenGL compositing."; init_ok = false; return; } if (!glPlatform->isGLES() && !m_backend->isSurfaceLessContext()) { glDrawBuffer(GL_BACK); } m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; initDebugOutput(); // set strict binding if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!glPlatform->supports(LooseBinding)); } bool haveSyncObjects = glPlatform->isGLES() ? hasGLVersion(3, 0) : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects && kwinApp()->operationMode() == Application::OperationModeX11) { const QByteArray useExplicitSync = qgetenv("KWIN_EXPLICIT_SYNC"); if (useExplicitSync != "0") { qCDebug(KWIN_OPENGL) << "Initializing fences for synchronization with the X command stream"; m_syncManager = new SyncManager; } else { qCDebug(KWIN_OPENGL) << "Explicit synchronization with the X command stream disabled by environment variable"; } } } static SceneOpenGL *gs_debuggedScene = nullptr; SceneOpenGL::~SceneOpenGL() { // do cleanup after initBuffer() gs_debuggedScene = nullptr; SceneOpenGL::EffectFrame::cleanup(); if (init_ok) { delete m_syncManager; // backend might be still needed for a different scene delete m_backend; } } static void scheduleVboReInit() { if (!gs_debuggedScene) return; static QPointer timer; if (!timer) { delete timer; timer = new QTimer(gs_debuggedScene); timer->setSingleShot(true); QObject::connect(timer.data(), &QTimer::timeout, gs_debuggedScene, []() { GLVertexBuffer::cleanup(); GLVertexBuffer::initStatic(); }); } timer->start(250); } void SceneOpenGL::initDebugOutput() { const bool have_KHR_debug = hasGLExtension(QByteArrayLiteral("GL_KHR_debug")); const bool have_ARB_debug = hasGLExtension(QByteArrayLiteral("GL_ARB_debug_output")); if (!have_KHR_debug && !have_ARB_debug) return; if (!have_ARB_debug) { // if we don't have ARB debug, but only KHR debug we need to verify whether the context is a debug context // it should work without as well, but empirical tests show: no it doesn't if (GLPlatform::instance()->isGLES()) { if (!hasGLVersion(3, 2)) { // empirical data shows extension doesn't work return; } } else if (!hasGLVersion(3, 0)) { return; } // can only be queried with either OpenGL >= 3.0 or OpenGL ES of at least 3.1 GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_DEBUG_BIT)) { return; } } gs_debuggedScene = this; // Set the callback function auto callback = [](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) { Q_UNUSED(source) Q_UNUSED(severity) Q_UNUSED(userParam) while (message[length] == '\n' || message[length] == '\r') --length; switch (type) { case GL_DEBUG_TYPE_ERROR: case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: qCWarning(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; case GL_DEBUG_TYPE_OTHER: // at least the nvidia driver seems prone to end up with invalid VBOs after // transferring them between system heap and VRAM // so we re-init them whenever this happens (typically when switching VT, resuming // from STR and XRandR events - #344326 if (strstr(message, "Buffer detailed info:") && strstr(message, "has been updated")) scheduleVboReInit(); // fall through! for general message printing Q_FALLTHROUGH(); case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: case GL_DEBUG_TYPE_PORTABILITY: case GL_DEBUG_TYPE_PERFORMANCE: default: qCDebug(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; } }; glDebugMessageCallback(callback, nullptr); // This state exists only in GL_KHR_debug if (have_KHR_debug) glEnable(GL_DEBUG_OUTPUT); #ifndef NDEBUG // Enable all debug messages glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); #else // Enable error messages glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); #endif // Insert a test message const QByteArray message = QByteArrayLiteral("OpenGL debug output initialized"); glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 0, GL_DEBUG_SEVERITY_LOW, message.length(), message.constData()); } SceneOpenGL *SceneOpenGL::createScene(QObject *parent) { OpenGLBackend *backend = kwinApp()->platform()->createOpenGLBackend(); if (!backend) { return nullptr; } if (!backend->isFailed()) { backend->init(); } if (backend->isFailed()) { delete backend; return NULL; } SceneOpenGL *scene = NULL; // first let's try an OpenGL 2 scene if (SceneOpenGL2::supported(backend)) { scene = new SceneOpenGL2(backend, parent); if (scene->initFailed()) { delete scene; scene = NULL; } else { return scene; } } if (!scene) { if (GLPlatform::instance()->recommendedCompositor() == XRenderCompositing) { qCCritical(KWIN_OPENGL) << "OpenGL driver recommends XRender based compositing. Falling back to XRender."; qCCritical(KWIN_OPENGL) << "To overwrite the detection use the environment variable KWIN_COMPOSE"; qCCritical(KWIN_OPENGL) << "For more information see http://community.kde.org/KWin/Environment_Variables#KWIN_COMPOSE"; } delete backend; } return scene; } OverlayWindow *SceneOpenGL::overlayWindow() { return m_backend->overlayWindow(); } bool SceneOpenGL::syncsToVBlank() const { return m_backend->syncsToVBlank(); } bool SceneOpenGL::blocksForRetrace() const { return m_backend->blocksForRetrace(); } void SceneOpenGL::idle() { m_backend->idle(); Scene::idle(); } bool SceneOpenGL::initFailed() const { return !init_ok; } void SceneOpenGL::handleGraphicsReset(GLenum status) { switch (status) { case GL_GUILTY_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset attributable to the current GL context occurred."; break; case GL_INNOCENT_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset not attributable to the current GL context occurred."; break; case GL_UNKNOWN_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset of an unknown cause occurred."; break; default: break; } QElapsedTimer timer; timer.start(); // Wait until the reset is completed or max 10 seconds while (timer.elapsed() < 10000 && glGetGraphicsResetStatus() != GL_NO_ERROR) usleep(50); qCDebug(KWIN_OPENGL) << "Attempting to reset compositing."; QMetaObject::invokeMethod(this, "resetCompositing", Qt::QueuedConnection); KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); } void SceneOpenGL::triggerFence() { if (m_syncManager) { m_currentFence = m_syncManager->nextFence(); m_currentFence->trigger(); } } void SceneOpenGL::insertWait() { if (m_currentFence && m_currentFence->state() != SyncObject::Waiting) { m_currentFence->wait(); } } /** * Render cursor texture in case hardware cursor is disabled. * Useful for screen recording apps or backends that can't do planes. */ void SceneOpenGL2::paintCursor() { // don't paint if we use hardware cursor if (!kwinApp()->platform()->usesSoftwareCursor()) { return; } // lazy init texture cursor only in case we need software rendering if (!m_cursorTexture) { auto updateCursorTexture = [this] { // don't paint if no image for cursor is set const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } m_cursorTexture.reset(new GLTexture(img)); }; // init now updateCursorTexture(); // handle shape update on case cursor image changed connect(Cursor::self(), &Cursor::cursorChanged, this, updateCursorTexture); } // get cursor position in projection coordinates const QPoint cursorPos = Cursor::pos() - kwinApp()->platform()->softwareCursorHotspot(); const QRect cursorRect(0, 0, m_cursorTexture->width(), m_cursorTexture->height()); QMatrix4x4 mvp = m_projectionMatrix; mvp.translate(cursorPos.x(), cursorPos.y()); // handle transparence glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // paint texture in cursor offset m_cursorTexture->bind(); ShaderBinder binder(ShaderTrait::MapTexture); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_cursorTexture->render(QRegion(cursorRect), cursorRect); m_cursorTexture->unbind(); kwinApp()->platform()->markCursorAsRendered(); glDisable(GL_BLEND); } qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) { // actually paint the frame, flushed with the NEXT frame createStackingOrder(toplevels); // After this call, updateRegion will contain the damaged region in the // back buffer. This is the region that needs to be posted to repair // the front buffer. It doesn't include the additional damage returned // by prepareRenderingFrame(). validRegion is the region that has been // repainted, and may be larger than updateRegion. QRegion updateRegion, validRegion; if (m_backend->perScreenRendering()) { // trigger start render timer m_backend->prepareRenderingFrame(); for (int i = 0; i < screens()->count(); ++i) { const QRect &geo = screens()->geometry(i); QRegion update; QRegion valid; // prepare rendering makes context current on the output QRegion repaint = m_backend->prepareRenderingForScreen(i); GLVertexBuffer::setVirtualScreenGeometry(geo); GLRenderTarget::setVirtualScreenGeometry(geo); GLVertexBuffer::setVirtualScreenScale(screens()->scale(i)); GLRenderTarget::setVirtualScreenScale(screens()->scale(i)); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix(), geo); // call generic implementation paintCursor(); GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrameForScreen(i, valid, update); GLVertexBuffer::streamingBuffer()->framePosted(); } } else { m_backend->makeCurrent(); QRegion repaint = m_backend->prepareRenderingFrame(); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } GLVertexBuffer::setVirtualScreenGeometry(screens()->geometry()); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); GLVertexBuffer::setVirtualScreenScale(1); GLRenderTarget::setVirtualScreenScale(1); int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage, repaint, &updateRegion, &validRegion, projectionMatrix()); // call generic implementation if (!GLPlatform::instance()->isGLES()) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); // copy dirty parts from front to backbuffer if (!m_backend->supportsBufferAge() && options->glPreferBufferSwap() == Options::CopyFrontBuffer && validRegion != displayRegion) { glReadBuffer(GL_FRONT); m_backend->copyPixels(displayRegion - validRegion); glReadBuffer(GL_BACK); validRegion = displayRegion; } } GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrame(validRegion, updateRegion); GLVertexBuffer::streamingBuffer()->framePosted(); } if (m_currentFence) { if (!m_syncManager->updateFences()) { qCDebug(KWIN_OPENGL) << "Aborting explicit synchronization with the X command stream."; qCDebug(KWIN_OPENGL) << "Future frames will be rendered unsynchronized."; delete m_syncManager; m_syncManager = nullptr; } m_currentFence = nullptr; } // do cleanup clearStackingOrder(); return m_backend->renderTime(); } QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const { QMatrix4x4 matrix; if (!(mask & PAINT_SCREEN_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation->applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } void SceneOpenGL::paintBackground(QRegion region) { PaintClipper pc(region); if (!PaintClipper::clip()) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); return; } if (pc.clip() && pc.paintArea().isEmpty()) return; // no background to paint QVector verts; for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { QRect r = iterator.boundingRect(); verts << r.x() + r.width() << r.y(); verts << r.x() << r.y(); verts << r.x() << r.y() + r.height(); verts << r.x() << r.y() + r.height(); verts << r.x() + r.width() << r.y() + r.height(); verts << r.x() + r.width() << r.y(); } doPaintBackground(verts); } void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { if (m_backend->supportsBufferAge()) return; const QSize &screenSize = screens()->size(); if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); uint damagedPixels = 0; const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*screenSize.width()*screenSize.height(); // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) // would be 57% of 5/4 foreach (const QRect &r, region.rects()) { // damagedPixels += r.width() * r.height(); // combined window damage test damagedPixels = r.width() * r.height(); // experimental single window damage testing if (damagedPixels > fullRepaintLimit) { region = displayRegion; return; } } } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint region = QRegion(0, 0, screenSize.width(), screenSize.height()); } } SceneOpenGLTexture *SceneOpenGL::createTexture() { return new SceneOpenGLTexture(m_backend); } bool SceneOpenGL::viewportLimitsMatched(const QSize &size) const { GLint limit[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, limit); if (limit[0] < size.width() || limit[1] < size.height()) { QMetaObject::invokeMethod(Compositor::self(), "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, Compositor::AllReasonSuspend)); const QString message = i18n("

OpenGL desktop effects not possible

" "Your system cannot perform OpenGL Desktop Effects at the " "current resolution

" "You can try to select the XRender backend, but it " "might be very slow for this resolution as well.
" "Alternatively, lower the combined resolution of all screens " "to %1x%2 ", limit[0], limit[1]); const QString details = i18n("The demanded resolution exceeds the GL_MAX_VIEWPORT_DIMS " "limitation of your GPU and is therefore not compatible " "with the OpenGL compositor.
" "XRender does not know such limitation, but the performance " "will usually be impacted by the hardware limitations that " "restrict the OpenGL viewport size."); const int oldTimeout = QDBusConnection::sessionBus().interface()->timeout(); QDBusConnection::sessionBus().interface()->setTimeout(500); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kwinCompositingDialog")).value()) { QDBusInterface dialog( QStringLiteral("org.kde.kwinCompositingDialog"), QStringLiteral("/CompositorSettings"), QStringLiteral("org.kde.kwinCompositingDialog") ); dialog.asyncCall(QStringLiteral("warn"), message, details, QString()); } else { const QString args = QLatin1String("warn ") + QString::fromUtf8(message.toLocal8Bit().toBase64()) + QLatin1String(" details ") + QString::fromUtf8(details.toLocal8Bit().toBase64()); KProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("kwincompositing") << QStringLiteral("--args") << args); } QDBusConnection::sessionBus().interface()->setTimeout(oldTimeout); return false; } glGetIntegerv(GL_MAX_TEXTURE_SIZE, limit); if (limit[0] < size.width() || limit[0] < size.height()) { KConfig cfg(QStringLiteral("kwin_dialogsrc")); if (!KConfigGroup(&cfg, "Notification Messages").readEntry("max_tex_warning", true)) return true; const QString message = i18n("

OpenGL desktop effects might be unusable

" "OpenGL Desktop Effects at the current resolution are supported " "but might be exceptionally slow.
" "Also large windows will turn entirely black.

" "Consider to suspend compositing, switch to the XRender backend " "or lower the resolution to %1x%1." , limit[0]); const QString details = i18n("The demanded resolution exceeds the GL_MAX_TEXTURE_SIZE " "limitation of your GPU, thus windows of that size cannot be " "assigned to textures and will be entirely black.
" "Also this limit will often be a performance level barrier despite " "below GL_MAX_VIEWPORT_DIMS, because the driver might fall back to " "software rendering in this case."); const int oldTimeout = QDBusConnection::sessionBus().interface()->timeout(); QDBusConnection::sessionBus().interface()->setTimeout(500); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kwinCompositingDialog")).value()) { QDBusInterface dialog( QStringLiteral("org.kde.kwinCompositingDialog"), QStringLiteral("/CompositorSettings"), QStringLiteral("org.kde.kwinCompositingDialog") ); dialog.asyncCall(QStringLiteral("warn"), message, details, QStringLiteral("kwin_dialogsrc:max_tex_warning")); } else { const QString args = QLatin1String("warn ") + QString::fromUtf8(message.toLocal8Bit().toBase64()) + QLatin1String(" details ") + QString::fromUtf8(details.toLocal8Bit().toBase64()) + QLatin1String(" dontagain kwin_dialogsrc:max_tex_warning"); KProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("kwincompositing") << QStringLiteral("--args") << args); } QDBusConnection::sessionBus().interface()->setTimeout(oldTimeout); } return true; } void SceneOpenGL::screenGeometryChanged(const QSize &size) { if (!viewportLimitsMatched(size)) return; Scene::screenGeometryChanged(size); glViewport(0,0, size.width(), size.height()); m_backend->screenGeometryChanged(size); GLRenderTarget::setVirtualScreenSize(size); } void SceneOpenGL::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { const QRect r = region.boundingRect(); glEnable(GL_SCISSOR_TEST); glScissor(r.x(), screens()->size().height() - r.y() - r.height(), r.width(), r.height()); KWin::Scene::paintDesktop(desktop, mask, region, data); glDisable(GL_SCISSOR_TEST); } bool SceneOpenGL::makeOpenGLContextCurrent() { return m_backend->makeCurrent(); } void SceneOpenGL::doneOpenGLContextCurrent() { m_backend->doneCurrent(); } Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame) { return new SceneOpenGL::EffectFrame(frame, this); } Shadow *SceneOpenGL::createShadow(Toplevel *toplevel) { return new SceneOpenGLShadow(toplevel); } Decoration::Renderer *SceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneOpenGLDecorationRenderer(impl); } bool SceneOpenGL::animationsSupported() const { return !GLPlatform::instance()->isSoftwareEmulation(); } QVector SceneOpenGL::openGLPlatformInterfaceExtensions() const { return m_backend->extensions().toVector(); } //**************************************** // SceneOpenGL2 //**************************************** bool SceneOpenGL2::supported(OpenGLBackend *backend) { const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); if (!forceEnv.isEmpty()) { if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing enforced by environment variable"; return true; } else { // OpenGL 2 disabled by environment variable return false; } } if (!backend->isDirectRendering()) { return false; } if (GLPlatform::instance()->recommendedCompositor() < OpenGL2Compositing) { qCDebug(KWIN_OPENGL) << "Driver does not recommend OpenGL 2 compositing"; return false; } return true; } SceneOpenGL2::SceneOpenGL2(OpenGLBackend *backend, QObject *parent) : SceneOpenGL(backend, parent) , m_lanczosFilter(NULL) { if (!init_ok) { // base ctor already failed return; } // We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects if (!hasGLVersion(2, 0)) { qCDebug(KWIN_OPENGL) << "OpenGL 2.0 is not supported"; init_ok = false; return; } const QSize &s = screens()->size(); GLRenderTarget::setVirtualScreenSize(s); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); // push one shader on the stack so that one is always bound ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); if (checkGLError("Init")) { qCCritical(KWIN_OPENGL) << "OpenGL 2 compositing setup failed"; init_ok = false; return; // error } // It is not legal to not have a vertex array object bound in a core context if (!GLPlatform::instance()->isGLES() && hasGLExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) { glGenVertexArrays(1, &vao); glBindVertexArray(vao); } if (!ShaderManager::instance()->selfTest()) { qCCritical(KWIN_OPENGL) << "ShaderManager self test failed"; init_ok = false; return; } qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing successfully initialized"; init_ok = true; } SceneOpenGL2::~SceneOpenGL2() { } QMatrix4x4 SceneOpenGL2::createProjectionMatrix() const { // Create a perspective projection with a 60° field-of-view, // and an aspect ratio of 1.0. const float fovY = 60.0f; const float aspect = 1.0f; const float zNear = 0.1f; const float zFar = 100.0f; const float yMax = zNear * std::tan(fovY * M_PI / 360.0f); const float yMin = -yMax; const float xMin = yMin * aspect; const float xMax = yMax * aspect; QMatrix4x4 projection; projection.frustum(xMin, xMax, yMin, yMax, zNear, zFar); // Create a second matrix that transforms screen coordinates // to world coordinates. const float scaleFactor = 1.1 * std::tan(fovY * M_PI / 360.0f) / yMax; const QSize size = screens()->size(); QMatrix4x4 matrix; matrix.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1); matrix.scale( (xMax - xMin) * scaleFactor / size.width(), -(yMax - yMin) * scaleFactor / size.height(), 0.001); // Combine the matrices return projection * matrix; } void SceneOpenGL2::updateProjectionMatrix() { m_projectionMatrix = createProjectionMatrix(); } void SceneOpenGL2::paintSimpleScreen(int mask, QRegion region) { m_screenProjectionMatrix = m_projectionMatrix; Scene::paintSimpleScreen(mask, region); } void SceneOpenGL2::paintGenericScreen(int mask, ScreenPaintData data) { const QMatrix4x4 screenMatrix = transformation(mask, data); m_screenProjectionMatrix = m_projectionMatrix * screenMatrix; Scene::paintGenericScreen(mask, data); } void SceneOpenGL2::doPaintBackground(const QVector< float >& vertices) { GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setData(vertices.count() / 2, 2, vertices.data(), NULL); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, m_projectionMatrix); vbo->render(GL_TRIANGLES); } Scene::Window *SceneOpenGL2::createWindow(Toplevel *t) { SceneOpenGL2Window *w = new SceneOpenGL2Window(t); w->setScene(this); return w; } void SceneOpenGL2::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } performPaintWindow(w, mask, region, data); } void SceneOpenGL2::performPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (mask & PAINT_WINDOW_LANCZOS) { if (!m_lanczosFilter) { m_lanczosFilter = new LanczosFilter(this); // reset the lanczos filter when the screen gets resized // it will get created next paint connect(screens(), &Screens::changed, this, [this]() { makeOpenGLContextCurrent(); delete m_lanczosFilter; m_lanczosFilter = NULL; }); } m_lanczosFilter->performPaint(w, mask, region, data); } else w->sceneWindow()->performPaint(mask, region, data); } //**************************************** // SceneOpenGL::Window //**************************************** SceneOpenGL::Window::Window(Toplevel* c) : Scene::Window(c) , m_scene(NULL) { } SceneOpenGL::Window::~Window() { } static SceneOpenGLTexture *s_frameTexture = NULL; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { s_frameTexture = NULL; OpenGLWindowPixmap *pixmap = windowPixmap(); if (!pixmap) { return false; } s_frameTexture = pixmap->texture(); if (pixmap->isDiscarded()) { return !pixmap->texture()->isNull(); } if (!window()->damage().isEmpty()) m_scene->insertWait(); return pixmap->bind(); } QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const { QMatrix4x4 matrix; matrix.translate(x(), y()); if (!(mask & PAINT_WINDOW_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation.applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } bool SceneOpenGL::Window::beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data) { if (region.isEmpty()) return false; m_hardwareClipping = region != infiniteRegion() && (mask & PAINT_WINDOW_TRANSFORMED) && !(mask & PAINT_SCREEN_TRANSFORMED); if (region != infiniteRegion() && !m_hardwareClipping) { WindowQuadList quads; quads.reserve(data.quads.count()); const QRegion filterRegion = region.translated(-x(), -y()); // split all quads in bounding rect with the actual rects in the region foreach (const WindowQuad &quad, data.quads) { foreach (const QRect &r, filterRegion.rects()) { const QRectF rf(r); const QRectF quadRect(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())); const QRectF &intersected = rf.intersected(quadRect); if (intersected.isValid()) { if (quadRect == intersected) { // case 1: completely contains, include and do not check other rects quads << quad; break; } // case 2: intersection quads << quad.makeSubQuad(intersected.left(), intersected.top(), intersected.right(), intersected.bottom()); } } } data.quads = quads; } if (data.quads.isEmpty()) return false; if (!bindTexture() || !s_frameTexture) { return false; } if (m_hardwareClipping) { glEnable(GL_SCISSOR_TEST); } // Update the texture filter if (options->glSmoothScale() != 0 && (mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) filter = ImageFilterGood; else filter = ImageFilterFast; s_frameTexture->setFilter(filter == ImageFilterGood ? GL_LINEAR : GL_NEAREST); const GLVertexAttrib attribs[] = { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); return true; } void SceneOpenGL::Window::endRenderWindow() { if (m_hardwareClipping) { glDisable(GL_SCISSOR_TEST); } } GLTexture *SceneOpenGL::Window::getDecorationTexture() const { if (AbstractClient *client = dynamic_cast(toplevel)) { if (client->noBorder()) { return nullptr; } if (!client->isDecorated()) { return nullptr; } if (SceneOpenGLDecorationRenderer *renderer = static_cast(client->decoratedClient()->renderer())) { renderer->render(); return renderer->texture(); } } else if (toplevel->isDeleted()) { Deleted *deleted = static_cast(toplevel); if (!deleted->wasClient() || deleted->noBorder()) { return nullptr; } if (const SceneOpenGLDecorationRenderer *renderer = static_cast(deleted->decorationRenderer())) { return renderer->texture(); } } return nullptr; } WindowPixmap* SceneOpenGL::Window::createWindowPixmap() { return new OpenGLWindowPixmap(this, m_scene); } //*************************************** // SceneOpenGL2Window //*************************************** SceneOpenGL2Window::SceneOpenGL2Window(Toplevel *c) : SceneOpenGL::Window(c) , m_blendingEnabled(false) { } SceneOpenGL2Window::~SceneOpenGL2Window() { } QVector4D SceneOpenGL2Window::modulate(float opacity, float brightness) const { const float a = opacity; const float rgb = opacity * brightness; return QVector4D(rgb, rgb, rgb, a); } void SceneOpenGL2Window::setBlendEnabled(bool enabled) { if (enabled && !m_blendingEnabled) glEnable(GL_BLEND); else if (!enabled && m_blendingEnabled) glDisable(GL_BLEND); m_blendingEnabled = enabled; } void SceneOpenGL2Window::setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data) { if (!quads[ShadowLeaf].isEmpty()) { nodes[ShadowLeaf].texture = static_cast(m_shadow)->shadowTexture(); nodes[ShadowLeaf].opacity = data.opacity(); nodes[ShadowLeaf].hasAlpha = true; nodes[ShadowLeaf].coordinateType = NormalizedCoordinates; } if (!quads[DecorationLeaf].isEmpty()) { nodes[DecorationLeaf].texture = getDecorationTexture(); nodes[DecorationLeaf].opacity = data.opacity(); nodes[DecorationLeaf].hasAlpha = true; nodes[DecorationLeaf].coordinateType = UnnormalizedCoordinates; } nodes[ContentLeaf].texture = s_frameTexture; nodes[ContentLeaf].hasAlpha = !isOpaque(); // TODO: ARGB crsoofading is atm. a hack, playing on opacities for two dumb SrcOver operations // Should be a shader if (data.crossFadeProgress() != 1.0 && (data.opacity() < 0.95 || toplevel->hasAlpha())) { const float opacity = 1.0 - data.crossFadeProgress(); nodes[ContentLeaf].opacity = data.opacity() * (1 - pow(opacity, 1.0f + 2.0f * data.opacity())); } else { nodes[ContentLeaf].opacity = data.opacity(); } nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); nodes[PreviousContentLeaf].texture = previous ? previous->texture() : NULL; nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); nodes[PreviousContentLeaf].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; } } QMatrix4x4 SceneOpenGL2Window::modelViewProjectionMatrix(int mask, const WindowPaintData &data) const { SceneOpenGL2 *scene = static_cast(m_scene); const QMatrix4x4 pMatrix = data.projectionMatrix(); const QMatrix4x4 mvMatrix = data.modelViewMatrix(); // An effect may want to override the default projection matrix in some cases, // such as when it is rendering a window on a render target that doesn't have // the same dimensions as the default framebuffer. // // Note that the screen transformation is not applied here. if (!pMatrix.isIdentity()) return pMatrix * mvMatrix; // If an effect has specified a model-view matrix, we multiply that matrix // with the default projection matrix. If the effect hasn't specified a // model-view matrix, mvMatrix will be the identity matrix. if (mask & Scene::PAINT_SCREEN_TRANSFORMED) return scene->screenProjectionMatrix() * mvMatrix; return scene->projectionMatrix() * mvMatrix; } static void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping) { QMatrix4x4 newWindowMatrix = windowMatrix; newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); qreal scale = 1.0; if (pixmap->surface()) { scale = pixmap->surface()->scale(); } if (!pixmap->texture()->isNull()) { // render this texture shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); auto texture = pixmap->texture(); texture->bind(); texture->render(region, QRect(0, 0, texture->width() / scale, texture->height() / scale), hardwareClipping); texture->unbind(); } const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap), region, hardwareClipping); } } void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!beginRenderWindow(mask, region, data)) return; QMatrix4x4 windowMatrix = transformation(mask, data); const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; GLShader *shader = data.shader; if (!shader) { ShaderTraits traits = ShaderTrait::MapTexture; if (data.opacity() != 1.0 || data.brightness() != 1.0 || data.crossFadeProgress() != 1.0) traits |= ShaderTrait::Modulate; if (data.saturation() != 1.0) traits |= ShaderTrait::AdjustSaturation; shader = ShaderManager::instance()->pushShader(traits); } shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); shader->setUniform(GLShader::Saturation, data.saturation()); const GLenum filter = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) && options->glSmoothScale() != 0 ? GL_LINEAR : GL_NEAREST; WindowQuadList quads[LeafCount]; // Split the quads into separate lists for each type foreach (const WindowQuad &quad, data.quads) { switch (quad.type()) { case WindowQuadDecoration: quads[DecorationLeaf].append(quad); continue; case WindowQuadContents: quads[ContentLeaf].append(quad); continue; case WindowQuadShadow: quads[ShadowLeaf].append(quad); continue; default: continue; } } if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); if (previous) { const QRect &oldGeometry = previous->contentsRect(); for (const WindowQuad &quad : quads[ContentLeaf]) { // we need to create new window quads with normalize texture coordinates // normal quads divide the x/y position by width/height. This would not work as the texture // is larger than the visible content in case of a decorated Client resulting in garbage being shown. // So we calculate the normalized texture coordinate in the Client's new content space and map it to // the previous Client's content space. WindowQuad newQuad(WindowQuadContents); for (int i = 0; i < 4; ++i) { const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); WindowVertex vertex(quad[i].x(), quad[i].y(), (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); newQuad[i] = vertex; } quads[PreviousContentLeaf].append(newQuad); } } } const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; const size_t size = verticesPerQuad * (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); LeafNode nodes[LeafCount]; setupLeafNodes(nodes, quads, data); for (int i = 0, v = 0; i < LeafCount; i++) { if (quads[i].isEmpty() || !nodes[i].texture) continue; nodes[i].firstVertex = v; nodes[i].vertexCount = quads[i].count() * verticesPerQuad; const QMatrix4x4 matrix = nodes[i].texture->matrix(nodes[i].coordinateType); quads[i].makeInterleavedArrays(primitiveType, &map[v], matrix); v += quads[i].count() * verticesPerQuad; } vbo->unmap(); vbo->bindArrays(); // Make sure the blend function is set up correctly in case we will be doing blending glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); float opacity = -1.0; for (int i = 0; i < LeafCount; i++) { if (nodes[i].vertexCount == 0) continue; setBlendEnabled(nodes[i].hasAlpha || nodes[i].opacity < 1.0); if (opacity != nodes[i].opacity) { shader->setUniform(GLShader::ModulationConstant, modulate(nodes[i].opacity, data.brightness())); opacity = nodes[i].opacity; } nodes[i].texture->setFilter(filter); nodes[i].texture->setWrapMode(GL_CLAMP_TO_EDGE); nodes[i].texture->bind(); vbo->draw(region, primitiveType, nodes[i].firstVertex, nodes[i].vertexCount, m_hardwareClipping); } vbo->unbindArrays(); setBlendEnabled(false); // render sub-surfaces auto wp = windowPixmap(); const auto &children = wp ? wp->children() : QVector(); windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap), region, m_hardwareClipping); } if (!data.shader) ShaderManager::instance()->popShader(); endRenderWindow(); } //**************************************** // OpenGLWindowPixmap //**************************************** OpenGLWindowPixmap::OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL* scene) : WindowPixmap(window) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene) : WindowPixmap(subSurface, parent) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::~OpenGLWindowPixmap() { } bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { // always call updateBuffer to get the sub-surface tree updated if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { updateBuffer(); } auto s = surface(); if (s && !s->trackedDamage().isEmpty()) { m_texture->updateFromPixmap(this); // mipmaps need to be updated m_texture->setDirty(); } if (subSurface().isNull()) { toplevel()->resetDamage(); } // also bind all children for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } return true; } // also bind all children, needs to be done before checking isValid // as there might be valid children to render, see https://bugreports.qt.io/browse/QTBUG-52192 if (subSurface().isNull()) { updateBuffer(); } for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } if (!isValid()) { return false; } bool success = m_texture->load(this); if (success) { if (subSurface().isNull()) { toplevel()->resetDamage(); } } else qCDebug(KWIN_OPENGL) << "Failed to bind window"; return success; } WindowPixmap *OpenGLWindowPixmap::createChild(const QPointer &subSurface) { return new OpenGLWindowPixmap(subSurface, this, m_scene); } bool OpenGLWindowPixmap::isValid() const { if (!m_texture->isNull()) { return true; } return WindowPixmap::isValid(); } //**************************************** // SceneOpenGL::EffectFrame //**************************************** GLTexture* SceneOpenGL::EffectFrame::m_unstyledTexture = NULL; QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = NULL; SceneOpenGL::EffectFrame::EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene) : Scene::EffectFrame(frame) , m_texture(NULL) , m_textTexture(NULL) , m_oldTextTexture(NULL) , m_textPixmap(NULL) , m_iconTexture(NULL) , m_oldIconTexture(NULL) , m_selectionTexture(NULL) , m_unstyledVBO(NULL) , m_scene(scene) { if (m_effectFrame->style() == EffectFrameUnstyled && !m_unstyledTexture) { updateUnstyledTexture(); } } SceneOpenGL::EffectFrame::~EffectFrame() { delete m_texture; delete m_textTexture; delete m_textPixmap; delete m_oldTextTexture; delete m_iconTexture; delete m_oldIconTexture; delete m_selectionTexture; delete m_unstyledVBO; } void SceneOpenGL::EffectFrame::free() { glFlush(); delete m_texture; m_texture = NULL; delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; delete m_iconTexture; m_iconTexture = NULL; delete m_selectionTexture; m_selectionTexture = NULL; delete m_unstyledVBO; m_unstyledVBO = NULL; delete m_oldIconTexture; m_oldIconTexture = NULL; delete m_oldTextTexture; m_oldTextTexture = NULL; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; m_selectionTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; m_textTexture = NULL; } void SceneOpenGL::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { if (m_effectFrame->geometry().isEmpty()) return; // Nothing to display region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL GLShader* shader = m_effectFrame->shader(); if (!shader) { shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); } else if (shader) { ShaderManager::instance()->pushShader(shader); } if (shader) { shader->setUniform(GLShader::ModulationConstant, QVector4D(1.0, 1.0, 1.0, 1.0)); shader->setUniform(GLShader::Saturation, 1.0f); } const QMatrix4x4 projection = m_scene->projectionMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { if (!m_unstyledVBO) { m_unstyledVBO = new GLVertexBuffer(GLVertexBuffer::Static); QRect area = m_effectFrame->geometry(); area.moveTo(0, 0); area.adjust(-5, -5, 5, 5); const int roundness = 5; QVector verts, texCoords; verts.reserve(84); texCoords.reserve(84); // top left verts << area.left() << area.top(); texCoords << 0.0f << 0.0f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; // top verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; // top right verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; // bottom left verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom right verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom(); texCoords << 1.0f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; // center verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; m_unstyledVBO->setData(verts.count() / 2, 2, verts.data(), texCoords.data()); } if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_unstyledTexture->bind(); const QPoint pt = m_effectFrame->geometry().topLeft(); QMatrix4x4 mvp(projection); mvp.translate(pt.x(), pt.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_unstyledVBO->render(region, GL_TRIANGLES); m_unstyledTexture->unbind(); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_texture) // Lazy creation updateTexture(); if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_texture->bind(); qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry const QRect rect = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); QMatrix4x4 mvp(projection); mvp.translate(rect.x(), rect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture->render(region, rect); m_texture->unbind(); } if (!m_effectFrame->selection().isNull()) { if (!m_selectionTexture) { // Lazy creation QPixmap pixmap = m_effectFrame->selectionFrame().framePixmap(); if (!pixmap.isNull()) m_selectionTexture = new GLTexture(pixmap); } if (m_selectionTexture) { if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->selection().x(), m_effectFrame->selection().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); m_selectionTexture->bind(); m_selectionTexture->render(region, m_effectFrame->selection()); m_selectionTexture->unbind(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); QMatrix4x4 mvp(projection); mvp.translate(topLeft.x(), topLeft.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldIconTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldIconTexture->bind(); m_oldIconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_oldIconTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_iconTexture) { // lazy creation m_iconTexture = new GLTexture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } m_iconTexture->bind(); m_iconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_iconTexture->unbind(); } // Render text if (!m_effectFrame->text().isEmpty()) { QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->geometry().x(), m_effectFrame->geometry().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldTextTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldTextTexture->bind(); m_oldTextTexture->render(region, m_effectFrame->geometry()); m_oldTextTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_textTexture) // Lazy creation updateTextTexture(); if (m_textTexture) { m_textTexture->bind(); m_textTexture->render(region, m_effectFrame->geometry()); m_textTexture->unbind(); } } if (shader) { ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } void SceneOpenGL::EffectFrame::updateTexture() { delete m_texture; m_texture = 0L; if (m_effectFrame->style() == EffectFrameStyled) { QPixmap pixmap = m_effectFrame->frame().framePixmap(); m_texture = new GLTexture(pixmap); } } void SceneOpenGL::EffectFrame::updateTextTexture() { delete m_textTexture; m_textTexture = 0L; delete m_textPixmap; m_textPixmap = 0L; if (m_effectFrame->text().isEmpty()) return; // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) rect.setLeft(m_effectFrame->iconSize().width()); // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->font()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } m_textPixmap = new QPixmap(m_effectFrame->geometry().size()); m_textPixmap->fill(Qt::transparent); QPainter p(m_textPixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) p.setPen(m_effectFrame->styledTextColor()); else // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textTexture = new GLTexture(*m_textPixmap); } void SceneOpenGL::EffectFrame::updateUnstyledTexture() { delete m_unstyledTexture; m_unstyledTexture = 0L; delete m_unstyledPixmap; m_unstyledPixmap = 0L; // Based off circle() from kwinxrenderutils.cpp #define CS 8 m_unstyledPixmap = new QPixmap(2 * CS, 2 * CS); m_unstyledPixmap->fill(Qt::transparent); QPainter p(m_unstyledPixmap); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(Qt::black); p.drawEllipse(m_unstyledPixmap->rect()); p.end(); #undef CS m_unstyledTexture = new GLTexture(*m_unstyledPixmap); } void SceneOpenGL::EffectFrame::cleanup() { delete m_unstyledTexture; m_unstyledTexture = NULL; delete m_unstyledPixmap; m_unstyledPixmap = NULL; } //**************************************** // SceneOpenGL::Shadow //**************************************** class DecorationShadowTextureCache { public: ~DecorationShadowTextureCache(); DecorationShadowTextureCache(const DecorationShadowTextureCache&) = delete; static DecorationShadowTextureCache &instance(); void unregister(SceneOpenGLShadow *shadow); QSharedPointer getTexture(SceneOpenGLShadow *shadow); private: DecorationShadowTextureCache() = default; struct Data { QSharedPointer texture; QVector shadows; }; QHash m_cache; }; DecorationShadowTextureCache &DecorationShadowTextureCache::instance() { static DecorationShadowTextureCache s_instance; return s_instance; } DecorationShadowTextureCache::~DecorationShadowTextureCache() { Q_ASSERT(m_cache.isEmpty()); } void DecorationShadowTextureCache::unregister(SceneOpenGLShadow *shadow) { auto it = m_cache.begin(); while (it != m_cache.end()) { auto &d = it.value(); // check whether the Vector of Shadows contains our shadow and remove all of them auto glIt = d.shadows.begin(); while (glIt != d.shadows.end()) { if (*glIt == shadow) { glIt = d.shadows.erase(glIt); } else { glIt++; } } // if there are no shadows any more we can erase the cache entry if (d.shadows.isEmpty()) { it = m_cache.erase(it); } else { it++; } } } QSharedPointer DecorationShadowTextureCache::getTexture(SceneOpenGLShadow *shadow) { Q_ASSERT(shadow->hasDecorationShadow()); unregister(shadow); const auto &decoShadow = shadow->decorationShadow(); Q_ASSERT(!decoShadow.isNull()); auto it = m_cache.find(decoShadow.data()); if (it != m_cache.end()) { Q_ASSERT(!it.value().shadows.contains(shadow)); it.value().shadows << shadow; return it.value().texture; } Data d; d.shadows << shadow; d.texture = QSharedPointer::create(shadow->decorationShadowImage()); m_cache.insert(decoShadow.data(), d); return d.texture; } SceneOpenGLShadow::SceneOpenGLShadow(Toplevel *toplevel) : Shadow(toplevel) { } SceneOpenGLShadow::~SceneOpenGLShadow() { if (effects) { effects->makeOpenGLContextCurrent(); DecorationShadowTextureCache::instance().unregister(this); m_texture.reset(); } } void SceneOpenGLShadow::buildQuads() { // prepare window quads m_shadowQuads.clear(); const QSizeF top(elementSize(ShadowElementTop)); const QSizeF topRight(elementSize(ShadowElementTopRight)); const QSizeF right(elementSize(ShadowElementRight)); const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); const QSizeF bottom(elementSize(ShadowElementBottom)); const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); const QSizeF left(elementSize(ShadowElementLeft)); const QSizeF topLeft(elementSize(ShadowElementTopLeft)); if ((left.width() - leftOffset() > topLevel()->width()) || (right.width() - rightOffset() > topLevel()->width()) || (top.height() - topOffset() > topLevel()->height()) || (bottom.height() - bottomOffset() > topLevel()->height())) { // if our shadow is bigger than the window, we don't render the shadow setShadowRegion(QRegion()); return; } const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), QPointF(topLevel()->width() + rightOffset(), topLevel()->height() + bottomOffset())); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); qreal tx1(0.0), tx2(0.0), ty1(0.0), ty2(0.0); tx2 = topLeft.width()/width; ty2 = topLeft.height()/height; WindowQuad topLeftQuad(WindowQuadShadow); topLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y(), tx1, ty1); topLeftQuad[ 1 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx2, ty1); topLeftQuad[ 2 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + topLeft.height(), tx2, ty2); topLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty2); m_shadowQuads.append(topLeftQuad); tx1 = tx2; tx2 = (topLeft.width() + top.width())/width; ty2 = top.height()/height; WindowQuad topQuad(WindowQuadShadow); topQuad[ 0 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx1, ty1); topQuad[ 1 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx2, ty1); topQuad[ 2 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + top.height(),tx2, ty2); topQuad[ 3 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + top.height(), tx1, ty2); m_shadowQuads.append(topQuad); tx1 = tx2; tx2 = 1.0; ty2 = topRight.height()/height; WindowQuad topRightQuad(WindowQuadShadow); topRightQuad[ 0 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx1, ty1); topRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y(), tx2, ty1); topRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty2); topRightQuad[ 3 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + topRight.height(), tx1, ty2); m_shadowQuads.append(topRightQuad); tx1 = (width - right.width())/width; ty1 = topRight.height()/height; ty2 = (topRight.height() + right.height())/height; WindowQuad rightQuad(WindowQuadShadow); rightQuad[ 0 ] = WindowVertex(outerRect.right() - right.width(), outerRect.y() + topRight.height(), tx1, ty1); rightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty1); rightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty2); rightQuad[ 3 ] = WindowVertex(outerRect.right() - right.width(), outerRect.bottom() - bottomRight.height(), tx1, ty2); m_shadowQuads.append(rightQuad); tx1 = (width - bottomRight.width())/width; ty1 = ty2; ty2 = 1.0; WindowQuad bottomRightQuad(WindowQuadShadow); bottomRightQuad[ 0 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottomRight.height(), tx1, ty1); bottomRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty1); bottomRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom(), tx2, ty2); bottomRightQuad[ 3 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomRightQuad); tx2 = tx1; tx1 = bottomLeft.width()/width; ty1 = (height - bottom.height())/height; WindowQuad bottomQuad(WindowQuadShadow); bottomQuad[ 0 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottom.height(), tx1, ty1); bottomQuad[ 1 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottom.height(), tx2, ty1); bottomQuad[ 2 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx2, ty2); bottomQuad[ 3 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomQuad); tx1 = 0.0; tx2 = bottomLeft.width()/width; ty1 = (height - bottomLeft.height())/height; WindowQuad bottomLeftQuad(WindowQuadShadow); bottomLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty1); bottomLeftQuad[ 1 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty1); bottomLeftQuad[ 2 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx2, ty2); bottomLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomLeftQuad); tx2 = left.width()/width; ty2 = ty1; ty1 = topLeft.height()/height; WindowQuad leftQuad(WindowQuadShadow); leftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty1); leftQuad[ 1 ] = WindowVertex(outerRect.x() + left.width(), outerRect.y() + topLeft.height(), tx2, ty1); leftQuad[ 2 ] = WindowVertex(outerRect.x() + left.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty2); leftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty2); m_shadowQuads.append(leftQuad); } bool SceneOpenGLShadow::prepareBackend() { if (hasDecorationShadow()) { // simplifies a lot by going directly to effects->makeOpenGLContextCurrent(); m_texture = DecorationShadowTextureCache::instance().getTexture(this); return true; } const QSize top(shadowPixmap(ShadowElementTop).size()); const QSize topRight(shadowPixmap(ShadowElementTopRight).size()); const QSize right(shadowPixmap(ShadowElementRight).size()); const QSize bottom(shadowPixmap(ShadowElementBottom).size()); const QSize bottomLeft(shadowPixmap(ShadowElementBottomLeft).size()); const QSize left(shadowPixmap(ShadowElementLeft).size()); const QSize topLeft(shadowPixmap(ShadowElementTopLeft).size()); const QSize bottomRight(shadowPixmap(ShadowElementBottomRight).size()); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); if (width == 0 || height == 0) { return false; } QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter p; p.begin(&image); p.drawPixmap(0, 0, shadowPixmap(ShadowElementTopLeft)); p.drawPixmap(topLeft.width(), 0, shadowPixmap(ShadowElementTop)); p.drawPixmap(topLeft.width() + top.width(), 0, shadowPixmap(ShadowElementTopRight)); p.drawPixmap(0, topLeft.height(), shadowPixmap(ShadowElementLeft)); p.drawPixmap(width - right.width(), topRight.height(), shadowPixmap(ShadowElementRight)); p.drawPixmap(0, topLeft.height() + left.height(), shadowPixmap(ShadowElementBottomLeft)); p.drawPixmap(bottomLeft.width(), height - bottom.height(), shadowPixmap(ShadowElementBottom)); p.drawPixmap(bottomLeft.width() + bottom.width(), topRight.height() + right.height(), shadowPixmap(ShadowElementBottomRight)); p.end(); // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) { QImage alphaImage(image.size(), QImage::Format_Indexed8); // Change to Format_Alpha8 w/ Qt 5.5 bool alphaOnly = true; for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) { const uint32_t * const src = reinterpret_cast(image.scanLine(y)); uint8_t * const dst = reinterpret_cast(alphaImage.scanLine(y)); for (ptrdiff_t x = 0; x < image.width(); x++) { if (src[x] & 0x00ffffff) alphaOnly = false; dst[x] = qAlpha(src[x]); } } if (alphaOnly) { image = alphaImage; } } effects->makeOpenGLContextCurrent(); m_texture = QSharedPointer::create(image); if (m_texture->internalFormat() == GL_R8) { // Swizzle red to alpha and all other channels to zero m_texture->bind(); m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED); } return true; } SceneOpenGLDecorationRenderer::SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) , m_texture() { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() = default; // Rotates the given source rect 90° counter-clockwise, // and flips it vertically static QImage rotate(const QImage &srcImage, const QRect &srcRect) { - QImage image(srcRect.height(), srcRect.width(), srcImage.format()); + auto dpr = srcImage.devicePixelRatio(); + QImage image(srcRect.height() * dpr, srcRect.width() * dpr, srcImage.format()); + image.setDevicePixelRatio(dpr); + const QPoint srcPoint(srcRect.x() * dpr, srcRect.y() * dpr); const uint32_t *src = reinterpret_cast(srcImage.bits()); uint32_t *dst = reinterpret_cast(image.bits()); for (int x = 0; x < image.width(); x++) { - const uint32_t *s = src + (srcRect.y() + x) * srcImage.width() + srcRect.x(); + const uint32_t *s = src + (srcPoint.y() + x) * srcImage.width() + srcPoint.x(); uint32_t *d = dst + x; for (int y = 0; y < image.height(); y++) { *d = s[y]; d += image.width(); } } return image; } void SceneOpenGLDecorationRenderer::render() { const QRegion scheduled = getScheduled(); - if (scheduled.isEmpty()) { + const bool dirty = areImageSizesDirty(); + if (scheduled.isEmpty() && !dirty) { return; } - const bool dirty = areImageSizesDirty(); if (dirty) { resizeTexture(); resetImageSizesDirty(); } if (!m_texture) { // for invalid sizes we get no texture, see BUG 361551 return; } QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); const QRect geometry = dirty ? QRect(QPoint(0, 0), client()->client()->geometry().size()) : scheduled.boundingRect(); auto renderPart = [this](const QRect &geo, const QRect &partRect, const QPoint &offset, bool rotated = false) { if (geo.isNull()) { return; } QImage image = renderToImage(geo); if (rotated) { // TODO: get this done directly when rendering to the image image = rotate(image, QRect(geo.topLeft() - partRect.topLeft(), geo.size())); } - m_texture->update(image, geo.topLeft() - partRect.topLeft() + offset); + m_texture->update(image, (geo.topLeft() - partRect.topLeft() + offset) * image.devicePixelRatio()); }; renderPart(left.intersected(geometry), left, QPoint(0, top.height() + bottom.height() + 2), true); renderPart(top.intersected(geometry), top, QPoint(0, 0)); renderPart(right.intersected(geometry), right, QPoint(0, top.height() + bottom.height() + left.width() + 3), true); renderPart(bottom.intersected(geometry), bottom, QPoint(0, top.height() + 1)); } static int align(int value, int align) { return (value + align - 1) & ~(align - 1); } void SceneOpenGLDecorationRenderer::resizeTexture() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); QSize size; size.rwidth() = qMax(qMax(top.width(), bottom.width()), qMax(left.height(), right.height())); size.rheight() = top.height() + bottom.height() + left.width() + right.width() + 3; size.rwidth() = align(size.width(), 128); + size *= client()->client()->screenScale(); if (m_texture && m_texture->size() == size) return; if (!size.isEmpty()) { m_texture.reset(new GLTexture(GL_RGBA8, size.width(), size.height())); m_texture->setYInverted(true); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->clear(); } else { m_texture.reset(); } } void SceneOpenGLDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } OpenGLFactory::OpenGLFactory(QObject *parent) : SceneFactory(parent) { } OpenGLFactory::~OpenGLFactory() = default; Scene *OpenGLFactory::create(QObject *parent) const { qCDebug(KWIN_OPENGL) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: if (kwinApp()->platform()->openGLCompositingIsBroken()) { qCWarning(KWIN_OPENGL) << "KWin has detected that your OpenGL library is unsafe to use"; return nullptr; } kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreInit); auto s = SceneOpenGL::createScene(parent); kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostInit); if (s && s->initFailed()) { delete s; return nullptr; } return s; } } // namespace diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp index aaf95d3be..69017c828 100644 --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -1,674 +1,674 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 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 "scene_qpainter.h" // KWin #include "client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "screens.h" #include "toplevel.h" #include "platform.h" #include "wayland_server.h" #include #include #include #include "decorations/decoratedclient.h" // Qt #include #include #include namespace KWin { //**************************************** // SceneQPainter //**************************************** SceneQPainter *SceneQPainter::createScene(QObject *parent) { QScopedPointer backend(kwinApp()->platform()->createQPainterBackend()); if (backend.isNull()) { return nullptr; } if (backend->isFailed()) { return NULL; } return new SceneQPainter(backend.take(), parent); } SceneQPainter::SceneQPainter(QPainterBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) , m_painter(new QPainter()) { } SceneQPainter::~SceneQPainter() { } CompositingType SceneQPainter::compositingType() const { return QPainterCompositing; } bool SceneQPainter::initFailed() const { return false; } void SceneQPainter::paintGenericScreen(int mask, ScreenPaintData data) { m_painter->save(); m_painter->translate(data.xTranslation(), data.yTranslation()); m_painter->scale(data.xScale(), data.yScale()); Scene::paintGenericScreen(mask, data); m_painter->restore(); } qint64 SceneQPainter::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; m_backend->prepareRenderingFrame(); if (m_backend->perScreenRendering()) { const bool needsFullRepaint = m_backend->needsFullRepaint(); if (needsFullRepaint) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion overallUpdate; for (int i = 0; i < screens()->count(); ++i) { const QRect geometry = screens()->geometry(i); QImage *buffer = m_backend->bufferForScreen(i); if (!buffer || buffer->isNull()) { continue; } m_painter->begin(buffer); m_painter->save(); m_painter->setWindow(geometry); QRegion updateRegion, validRegion; paintScreen(&mask, damage.intersected(geometry), QRegion(), &updateRegion, &validRegion); overallUpdate = overallUpdate.united(updateRegion); paintCursor(); m_painter->restore(); m_painter->end(); } m_backend->showOverlay(); m_backend->present(mask, overallUpdate); } else { m_painter->begin(m_backend->buffer()); m_painter->setClipping(true); m_painter->setClipRegion(damage); if (m_backend->needsFullRepaint()) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); paintCursor(); m_backend->showOverlay(); m_painter->end(); m_backend->present(mask, updateRegion); } // do cleanup clearStackingOrder(); emit frameRendered(); return renderTimer.nsecsElapsed(); } void SceneQPainter::paintBackground(QRegion region) { m_painter->setBrush(Qt::black); m_painter->drawRects(region.rects()); } void SceneQPainter::paintCursor() { if (!kwinApp()->platform()->usesSoftwareCursor()) { return; } const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } const QPoint cursorPos = Cursor::pos(); const QPoint hotspot = kwinApp()->platform()->softwareCursorHotspot(); m_painter->drawImage(cursorPos - hotspot, img); kwinApp()->platform()->markCursorAsRendered(); } Scene::Window *SceneQPainter::createWindow(Toplevel *toplevel) { return new SceneQPainter::Window(this, toplevel); } Scene::EffectFrame *SceneQPainter::createEffectFrame(EffectFrameImpl *frame) { return new QPainterEffectFrame(frame, this); } Shadow *SceneQPainter::createShadow(Toplevel *toplevel) { return new SceneQPainterShadow(toplevel); } void SceneQPainter::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } QImage *SceneQPainter::qpainterRenderBuffer() const { return m_backend->buffer(); } //**************************************** // SceneQPainter::Window //**************************************** SceneQPainter::Window::Window(SceneQPainter *scene, Toplevel *c) : Scene::Window(c) , m_scene(scene) { } SceneQPainter::Window::~Window() { discardShape(); } static void paintSubSurface(QPainter *painter, const QPoint &pos, QPainterWindowPixmap *pixmap) { QPoint p = pos; if (!pixmap->subSurface().isNull()) { p += pixmap->subSurface()->position(); } painter->drawImage(QRect(pos, pixmap->size()), pixmap->image()); const auto &children = pixmap->children(); for (auto it = children.begin(); it != children.end(); ++it) { auto pixmap = static_cast(*it); if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, p, pixmap); } } void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; QPainterWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } if (!toplevel->damage().isEmpty()) { pixmap->updateBuffer(); toplevel->resetDamage(); } QPainter *scenePainter = m_scene->scenePainter(); QPainter *painter = scenePainter; painter->save(); painter->setClipRegion(region); painter->setClipping(true); painter->translate(x(), y()); if (mask & PAINT_WINDOW_TRANSFORMED) { painter->translate(data.xTranslation(), data.yTranslation()); painter->scale(data.xScale(), data.yScale()); } const bool opaque = qFuzzyCompare(1.0, data.opacity()); QImage tempImage; QPainter tempPainter; if (!opaque) { // need a temp render target which we later on blit to the screen tempImage = QImage(toplevel->visibleRect().size(), QImage::Format_ARGB32_Premultiplied); tempImage.fill(Qt::transparent); tempPainter.begin(&tempImage); tempPainter.save(); tempPainter.translate(toplevel->geometry().topLeft() - toplevel->visibleRect().topLeft()); painter = &tempPainter; } renderShadow(painter); renderWindowDecorations(painter); // render content const QRect target = QRect(toplevel->clientPos(), toplevel->clientSize()); QSize srcSize = pixmap->image().size(); if (pixmap->surface() && pixmap->surface()->scale() == 1 && srcSize != toplevel->clientSize()) { // special case for XWayland windows srcSize = toplevel->clientSize(); } const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), srcSize); painter->drawImage(target, pixmap->image(), src); // render subsurfaces const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); } if (!opaque) { tempPainter.restore(); tempPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); QColor translucent(Qt::transparent); translucent.setAlphaF(data.opacity()); tempPainter.fillRect(QRect(QPoint(0, 0), toplevel->visibleRect().size()), translucent); tempPainter.end(); painter = scenePainter; painter->drawImage(toplevel->visibleRect().topLeft() - toplevel->geometry().topLeft(), tempImage); } painter->restore(); } void SceneQPainter::Window::renderShadow(QPainter* painter) { if (!toplevel->shadow()) { return; } SceneQPainterShadow *shadow = static_cast(toplevel->shadow()); const QPixmap &topLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopLeft); const QPixmap &top = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTop); const QPixmap &topRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopRight); const QPixmap &bottomLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomLeft); const QPixmap &bottom = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottom); const QPixmap &bottomRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomRight); const QPixmap &left = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementLeft); const QPixmap &right = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementRight); const int leftOffset = shadow->leftOffset(); const int topOffset = shadow->topOffset(); const int rightOffset = shadow->rightOffset(); const int bottomOffset = shadow->bottomOffset(); // top left painter->drawPixmap(-leftOffset, -topOffset, topLeft); // top right painter->drawPixmap(toplevel->width() - topRight.width() + rightOffset, -topOffset, topRight); // bottom left painter->drawPixmap(-leftOffset, toplevel->height() - bottomLeft.height() + bottomOffset, bottomLeft); // bottom right painter->drawPixmap(toplevel->width() - bottomRight.width() + rightOffset, toplevel->height() - bottomRight.height() + bottomOffset, bottomRight); // top painter->drawPixmap(topLeft.width() - leftOffset, -topOffset, toplevel->width() - topLeft.width() - topRight.width() + leftOffset + rightOffset, top.height(), top); // left painter->drawPixmap(-leftOffset, topLeft.height() - topOffset, left.width(), toplevel->height() - topLeft.height() - bottomLeft.height() + topOffset + bottomOffset, left); // right painter->drawPixmap(toplevel->width() - right.width() + rightOffset, topRight.height() - topOffset, right.width(), toplevel->height() - topRight.height() - bottomRight.height() + topOffset + bottomOffset, right); // bottom painter->drawPixmap(bottomLeft.width() - leftOffset, toplevel->height() - bottom.height() + bottomOffset, toplevel->width() - bottomLeft.width() - bottomRight.width() + leftOffset + rightOffset, bottom.height(), bottom); } void SceneQPainter::Window::renderWindowDecorations(QPainter *painter) { // TODO: custom decoration opacity AbstractClient *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); if (!client && !deleted) { return; } bool noBorder = true; const SceneQPainterDecorationRenderer *renderer = nullptr; QRect dtr, dlr, drr, dbr; if (client && !client->noBorder()) { if (client->isDecorated()) { if (SceneQPainterDecorationRenderer *r = static_cast(client->decoratedClient()->renderer())) { r->render(); renderer = r; } } client->layoutDecorationRects(dlr, dtr, drr, dbr); noBorder = false; } else if (deleted && !deleted->noBorder()) { noBorder = false; deleted->layoutDecorationRects(dlr, dtr, drr, dbr); renderer = static_cast(deleted->decorationRenderer()); } if (noBorder || !renderer) { return; } painter->drawImage(dtr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Top)); painter->drawImage(dlr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Left)); painter->drawImage(drr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Right)); painter->drawImage(dbr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Bottom)); } WindowPixmap *SceneQPainter::Window::createWindowPixmap() { return new QPainterWindowPixmap(this); } Decoration::Renderer *SceneQPainter::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneQPainterDecorationRenderer(impl); } //**************************************** // QPainterWindowPixmap //**************************************** QPainterWindowPixmap::QPainterWindowPixmap(Scene::Window *window) : WindowPixmap(window) { } QPainterWindowPixmap::QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : WindowPixmap(subSurface, parent) { } QPainterWindowPixmap::~QPainterWindowPixmap() { } void QPainterWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } // performing deep copy, this could probably be improved m_image = buffer()->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } WindowPixmap *QPainterWindowPixmap::createChild(const QPointer &subSurface) { return new QPainterWindowPixmap(subSurface, this); } void QPainterWindowPixmap::updateBuffer() { const auto oldBuffer = buffer(); WindowPixmap::updateBuffer(); const auto &b = buffer(); if (b.isNull()) { m_image = QImage(); return; } if (b == oldBuffer) { return; } // perform deep copy m_image = b->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } bool QPainterWindowPixmap::isValid() const { if (!m_image.isNull()) { return true; } return WindowPixmap::isValid(); } QPainterEffectFrame::QPainterEffectFrame(EffectFrameImpl *frame, SceneQPainter *scene) : Scene::EffectFrame(frame) , m_scene(scene) { } QPainterEffectFrame::~QPainterEffectFrame() { } void QPainterEffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) Q_UNUSED(opacity) // TODO: adjust opacity if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } QPainter *painter = m_scene->scenePainter(); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { painter->save(); painter->setPen(Qt::NoPen); QColor color(Qt::black); color.setAlphaF(frameOpacity); painter->setBrush(color); painter->setRenderHint(QPainter::Antialiasing); painter->drawRoundedRect(m_effectFrame->geometry().adjusted(-5, -5, 5, 5), 5.0, 5.0); painter->restore(); } else if (m_effectFrame->style() == EffectFrameStyled) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); painter->drawPixmap(geom, m_effectFrame->frame().framePixmap()); } if (!m_effectFrame->selection().isNull()) { painter->drawPixmap(m_effectFrame->selection(), m_effectFrame->selectionFrame().framePixmap()); } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { const QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); const QRect geom = QRect(topLeft, m_effectFrame->iconSize()); painter->drawPixmap(geom, m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } // Render text if (!m_effectFrame->text().isEmpty()) { // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } painter->save(); painter->setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { painter->setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required painter->setPen(Qt::white); } painter->drawText(rect.translated(m_effectFrame->geometry().topLeft()), m_effectFrame->alignment(), text); painter->restore(); } } //**************************************** // QPainterShadow //**************************************** SceneQPainterShadow::SceneQPainterShadow(Toplevel* toplevel) : Shadow(toplevel) { } SceneQPainterShadow::~SceneQPainterShadow() { } bool SceneQPainterShadow::prepareBackend() { if (hasDecorationShadow()) { // TODO: implement for QPainter return false; } return true; } //**************************************** // QPainterDecorationRenderer //**************************************** SceneQPainterDecorationRenderer::SceneQPainterDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneQPainterDecorationRenderer::~SceneQPainterDecorationRenderer() = default; QImage SceneQPainterDecorationRenderer::image(SceneQPainterDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); return m_images[int(part)]; } void SceneQPainterDecorationRenderer::render() { const QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizeImages(); resetImageSizesDirty(); } auto imageSize = [this](DecorationPart part) { return m_images[int(part)].size() / m_images[int(part)].devicePixelRatio(); }; const QRect top(QPoint(0, 0), imageSize(DecorationPart::Top)); const QRect left(QPoint(0, top.height()), imageSize(DecorationPart::Left)); const QRect right(QPoint(top.width() - imageSize(DecorationPart::Right).width(), top.height()), imageSize(DecorationPart::Right)); const QRect bottom(QPoint(0, left.y() + left.height()), imageSize(DecorationPart::Bottom)); const QRect geometry = scheduled.boundingRect(); auto renderPart = [this](const QRect &rect, const QRect &partRect, int index) { if (rect.isEmpty()) { return; } QPainter painter(&m_images[index]); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(QRect(partRect.topLeft(), partRect.size() * m_images[index].devicePixelRatio())); painter.setClipRect(rect); painter.save(); // clear existing part painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(rect, Qt::transparent); painter.restore(); client()->decoration()->paint(&painter, rect); }; renderPart(left.intersected(geometry), left, int(DecorationPart::Left)); renderPart(top.intersected(geometry), top, int(DecorationPart::Top)); renderPart(right.intersected(geometry), right, int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom, int(DecorationPart::Bottom)); } void SceneQPainterDecorationRenderer::resizeImages() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); auto checkAndCreate = [this](int index, const QSize &size) { - auto dpr = screens()->scale(client()->client()->screen()); + auto dpr = client()->client()->screenScale(); if (m_images[index].size() != size * dpr || m_images[index].devicePixelRatio() != dpr) { m_images[index] = QImage(size * dpr, QImage::Format_ARGB32_Premultiplied); m_images[index].setDevicePixelRatio(dpr); m_images[index].fill(Qt::transparent); } }; checkAndCreate(int(DecorationPart::Left), left.size()); checkAndCreate(int(DecorationPart::Right), right.size()); checkAndCreate(int(DecorationPart::Top), top.size()); checkAndCreate(int(DecorationPart::Bottom), bottom.size()); } void SceneQPainterDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } QPainterFactory::QPainterFactory(QObject *parent) : SceneFactory(parent) { } QPainterFactory::~QPainterFactory() = default; Scene *QPainterFactory::create(QObject *parent) const { auto s = SceneQPainter::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // KWin diff --git a/plugins/scenes/xrender/scene_xrender.cpp b/plugins/scenes/xrender/scene_xrender.cpp index df1702699..7e0864a07 100644 --- a/plugins/scenes/xrender/scene_xrender.cpp +++ b/plugins/scenes/xrender/scene_xrender.cpp @@ -1,1325 +1,1326 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Fredrik Höglund Copyright (C) 2013 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 "scene_xrender.h" #include "utils.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "logging.h" #include "toplevel.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "overlaywindow.h" #include "platform.h" #include "screens.h" #include "xcbutils.h" #include "kwinxrenderutils.h" #include "decorations/decoratedclient.h" #include #include #include #include namespace KWin { ScreenPaintData SceneXrender::screen_paint; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) #define FIXED_TO_DOUBLE(f) ((double) ((f) / 65536.0)) //**************************************** // XRenderBackend //**************************************** XRenderBackend::XRenderBackend() : m_buffer(XCB_RENDER_PICTURE_NONE) , m_failed(false) { if (!Xcb::Extensions::self()->isRenderAvailable()) { setFailed("No XRender extension available"); return; } if (!Xcb::Extensions::self()->isFixesRegionAvailable()) { setFailed("No XFixes v3+ extension available"); return; } } XRenderBackend::~XRenderBackend() { if (m_buffer) { xcb_render_free_picture(connection(), m_buffer); } } OverlayWindow* XRenderBackend::overlayWindow() { return NULL; } void XRenderBackend::showOverlay() { } void XRenderBackend::setBuffer(xcb_render_picture_t buffer) { if (m_buffer != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_buffer); } m_buffer = buffer; } void XRenderBackend::setFailed(const QString& reason) { qCCritical(KWIN_XRENDER) << "Creating the XRender backend failed: " << reason; m_failed = true; } void XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) } //**************************************** // X11XRenderBackend //**************************************** X11XRenderBackend::X11XRenderBackend() : XRenderBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , m_front(XCB_RENDER_PICTURE_NONE) , m_format(0) { init(true); } X11XRenderBackend::~X11XRenderBackend() { if (m_front) { xcb_render_free_picture(connection(), m_front); } m_overlayWindow->destroy(); } OverlayWindow* X11XRenderBackend::overlayWindow() { return m_overlayWindow.data(); } void X11XRenderBackend::showOverlay() { if (m_overlayWindow->window()) // show the window only after the first pass, since m_overlayWindow->show(); // that pass may take long } void X11XRenderBackend::init(bool createOverlay) { if (m_front != XCB_RENDER_PICTURE_NONE) xcb_render_free_picture(connection(), m_front); bool haveOverlay = createOverlay ? m_overlayWindow->create() : (m_overlayWindow->window() != XCB_WINDOW_NONE); if (haveOverlay) { m_overlayWindow->setup(XCB_WINDOW_NONE); ScopedCPointer attribs(xcb_get_window_attributes_reply(connection(), xcb_get_window_attributes_unchecked(connection(), m_overlayWindow->window()), NULL)); if (!attribs) { setFailed("Failed getting window attributes for overlay window"); return; } m_format = XRenderUtils::findPictFormat(attribs->visual); if (m_format == 0) { setFailed("Failed to find XRender format for overlay window"); return; } m_front = xcb_generate_id(connection()); xcb_render_create_picture(connection(), m_front, m_overlayWindow->window(), m_format, 0, NULL); } else { // create XRender picture for the root window m_format = XRenderUtils::findPictFormat(defaultScreen()->root_visual); if (m_format == 0) { setFailed("Failed to find XRender format for root window"); return; // error } m_front = xcb_generate_id(connection()); const uint32_t values[] = {XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS}; xcb_render_create_picture(connection(), m_front, rootWindow(), m_format, XCB_RENDER_CP_SUBWINDOW_MODE, values); } createBuffer(); } void X11XRenderBackend::createBuffer() { xcb_pixmap_t pixmap = xcb_generate_id(connection()); const auto displaySize = screens()->displaySize(); xcb_create_pixmap(connection(), Xcb::defaultDepth(), pixmap, rootWindow(), displaySize.width(), displaySize.height()); xcb_render_picture_t b = xcb_generate_id(connection()); xcb_render_create_picture(connection(), b, pixmap, m_format, 0, NULL); xcb_free_pixmap(connection(), pixmap); // The picture owns the pixmap now setBuffer(b); } void X11XRenderBackend::present(int mask, const QRegion &damage) { const auto displaySize = screens()->displaySize(); if (mask & Scene::PAINT_SCREEN_REGION) { // Use the damage region as the clip region for the root window XFixesRegion frontRegion(damage); xcb_xfixes_set_picture_clip_region(connection(), m_front, frontRegion, 0, 0); // copy composed buffer to the root window xcb_xfixes_set_picture_clip_region(connection(), buffer(), XCB_XFIXES_REGION_NONE, 0, 0); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_xfixes_set_picture_clip_region(connection(), m_front, XCB_XFIXES_REGION_NONE, 0, 0); xcb_flush(connection()); } else { // copy composed buffer to the root window xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_flush(connection()); } } void X11XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) init(false); } bool X11XRenderBackend::usesOverlayWindow() const { return true; } //**************************************** // SceneXrender //**************************************** SceneXrender* SceneXrender::createScene(QObject *parent) { QScopedPointer backend; backend.reset(new X11XRenderBackend); if (backend->isFailed()) { return NULL; } return new SceneXrender(backend.take(), parent); } SceneXrender::SceneXrender(XRenderBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) { } SceneXrender::~SceneXrender() { SceneXrender::Window::cleanup(); SceneXrender::EffectFrame::cleanup(); } bool SceneXrender::initFailed() const { return false; } // the entry point for painting qint64 SceneXrender::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); m_backend->showOverlay(); m_backend->present(mask, updateRegion); // do cleanup clearStackingOrder(); return renderTimer.nsecsElapsed(); } void SceneXrender::paintGenericScreen(int mask, ScreenPaintData data) { screen_paint = data; // save, transformations will be done when painting windows Scene::paintGenericScreen(mask, data); } void SceneXrender::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { PaintClipper::push(region); KWin::Scene::paintDesktop(desktop, mask, region, data); PaintClipper::pop(region); } // fill the screen background void SceneXrender::paintBackground(QRegion region) { xcb_render_color_t col = { 0, 0, 0, 0xffff }; // black const QVector &rects = Xcb::regionToRects(region); xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, xrenderBufferPicture(), col, rects.count(), rects.data()); } Scene::Window *SceneXrender::createWindow(Toplevel *toplevel) { return new Window(toplevel, this); } Scene::EffectFrame *SceneXrender::createEffectFrame(EffectFrameImpl *frame) { return new SceneXrender::EffectFrame(frame); } Shadow *SceneXrender::createShadow(Toplevel *toplevel) { return new SceneXRenderShadow(toplevel); } Decoration::Renderer *SceneXrender::createDecorationRenderer(Decoration::DecoratedClientImpl* client) { return new SceneXRenderDecorationRenderer(client); } //**************************************** // SceneXrender::Window //**************************************** XRenderPicture *SceneXrender::Window::s_tempPicture = 0; QRect SceneXrender::Window::temp_visibleRect; XRenderPicture *SceneXrender::Window::s_fadeAlphaPicture = nullptr; SceneXrender::Window::Window(Toplevel* c, SceneXrender *scene) : Scene::Window(c) , m_scene(scene) , format(XRenderUtils::findPictFormat(c->visual())) { } SceneXrender::Window::~Window() { discardShape(); } void SceneXrender::Window::cleanup() { delete s_tempPicture; s_tempPicture = NULL; delete s_fadeAlphaPicture; s_fadeAlphaPicture = nullptr; } // Maps window coordinates to screen coordinates QRect SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QRect &rect) const { QRect r = rect; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation r.moveTo(r.x() * data.xScale() + data.xTranslation(), r.y() * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); } // Move the rectangle to the screen position r.translate(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation r.moveTo(r.x() * screen_paint.xScale() + screen_paint.xTranslation(), r.y() * screen_paint.yScale() + screen_paint.yTranslation()); r.setWidth(r.width() * screen_paint.xScale()); r.setHeight(r.height() * screen_paint.yScale()); } return r; } // Maps window coordinates to screen coordinates QPoint SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QPoint &point) const { QPoint pt = point; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation pt.rx() = pt.x() * data.xScale() + data.xTranslation(); pt.ry() = pt.y() * data.yScale() + data.yTranslation(); } // Move the point to the screen position pt += QPoint(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation pt.rx() = pt.x() * screen_paint.xScale() + screen_paint.xTranslation(); pt.ry() = pt.y() * screen_paint.yScale() + screen_paint.yTranslation(); } return pt; } void SceneXrender::Window::prepareTempPixmap() { const QSize oldSize = temp_visibleRect.size(); temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); if (s_tempPicture && (oldSize.width() < temp_visibleRect.width() || oldSize.height() < temp_visibleRect.height())) { delete s_tempPicture; s_tempPicture = NULL; scene_setXRenderOffscreenTarget(0); // invalidate, better crash than cause weird results for developers } if (!s_tempPicture) { xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), temp_visibleRect.width(), temp_visibleRect.height()); s_tempPicture = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); } const xcb_render_color_t transparent = {0, 0, 0, 0}; const xcb_rectangle_t rect = {0, 0, uint16_t(temp_visibleRect.width()), uint16_t(temp_visibleRect.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_tempPicture, transparent, 1, &rect); } // paint the window void SceneXrender::Window::performPaint(int mask, QRegion region, WindowPaintData data) { setTransformedShape(QRegion()); // maybe nothing will be painted // check if there is something to paint bool opaque = isOpaque() && qFuzzyCompare(data.opacity(), 1.0); /* HACK: It seems this causes painting glitches, disable temporarily if (( mask & PAINT_WINDOW_OPAQUE ) ^ ( mask & PAINT_WINDOW_TRANSLUCENT )) { // We are only painting either opaque OR translucent windows, not both if ( mask & PAINT_WINDOW_OPAQUE && !opaque ) return; // Only painting opaque and window is translucent if ( mask & PAINT_WINDOW_TRANSLUCENT && opaque ) return; // Only painting translucent and window is opaque }*/ // Intersect the clip region with the rectangle the window occupies on the screen if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; XRenderWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } xcb_render_picture_t pic = pixmap->picture(); if (pic == XCB_RENDER_PICTURE_NONE) // The render format can be null for GL and/or Xv visuals return; toplevel->resetDamage(); // set picture filter if (options->isXrenderSmoothScale()) { // only when forced, it's slow if (mask & PAINT_WINDOW_TRANSFORMED) filter = ImageFilterGood; else if (mask & PAINT_SCREEN_TRANSFORMED) filter = ImageFilterGood; else filter = ImageFilterFast; } else filter = ImageFilterFast; // do required transformations const QRect wr = mapToScreen(mask, data, QRect(0, 0, width(), height())); QRect cr = QRect(toplevel->clientPos(), toplevel->clientSize()); // Client rect (in the window) qreal xscale = 1; qreal yscale = 1; bool scaled = false; Client *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); const QRect decorationRect = toplevel->decorationRect(); if (((client && !client->noBorder()) || (deleted && !deleted->noBorder())) && true) { // decorated client transformed_shape = decorationRect; if (toplevel->shape()) { // "xeyes" + decoration transformed_shape -= cr; transformed_shape += shape(); } } else { transformed_shape = shape(); } if (toplevel->hasShadow()) transformed_shape |= toplevel->shadow()->shadowRegion(); xcb_render_transform_t xform = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; static const xcb_render_transform_t identity = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; if (mask & PAINT_WINDOW_TRANSFORMED) { xscale = data.xScale(); yscale = data.yScale(); } if (mask & PAINT_SCREEN_TRANSFORMED) { xscale *= screen_paint.xScale(); yscale *= screen_paint.yScale(); } if (!qFuzzyCompare(xscale, 1.0) || !qFuzzyCompare(yscale, 1.0)) { scaled = true; xform.matrix11 = DOUBLE_TO_FIXED(1.0 / xscale); xform.matrix22 = DOUBLE_TO_FIXED(1.0 / yscale); // transform the shape for clipping in paintTransformedScreen() QVector rects = transformed_shape.rects(); for (int i = 0; i < rects.count(); ++i) { QRect& r = rects[ i ]; r.setRect(qRound(r.x() * xscale), qRound(r.y() * yscale), qRound(r.width() * xscale), qRound(r.height() * yscale)); } transformed_shape.setRects(rects.constData(), rects.count()); } transformed_shape.translate(mapToScreen(mask, data, QPoint(0, 0))); PaintClipper pcreg(region); // clip by the region to paint PaintClipper pc(transformed_shape); // clip by window's shape const bool wantShadow = m_shadow && !m_shadow->shadowRegion().isEmpty(); // In order to obtain a pixel perfect rescaling // we need to blit the window content togheter with // decorations in a temporary pixmap and scale // the temporary pixmap at the end. // We should do this only if there is scaling and // the window has border // This solves a number of glitches and on top of this // it optimizes painting quite a bit const bool blitInTempPixmap = xRenderOffscreen() || (data.crossFadeProgress() < 1.0 && !opaque) || (scaled && (wantShadow || (client && !client->noBorder()) || (deleted && !deleted->noBorder()))); xcb_render_picture_t renderTarget = m_scene->xrenderBufferPicture(); if (blitInTempPixmap) { if (scene_xRenderOffscreenTarget()) { temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); renderTarget = *scene_xRenderOffscreenTarget(); } else { prepareTempPixmap(); renderTarget = *s_tempPicture; } } else { xcb_render_set_picture_transform(connection(), pic, xform); if (filter == ImageFilterGood) { setPictureFilter(pic, KWin::Scene::ImageFilterGood); } //BEGIN OF STUPID RADEON HACK // This is needed to avoid hitting a fallback in the radeon driver. // The Render specification states that sampling pixels outside the // source picture results in alpha=0 pixels. This can be achieved by // setting the border color to transparent black, but since the border // color has the same format as the texture, it only works when the // texture has an alpha channel. So the driver falls back to software // when the repeat mode is RepeatNone, the picture has a non-identity // transformation matrix, and doesn't have an alpha channel. // Since we only scale the picture, we can work around this by setting // the repeat mode to RepeatPad. if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_PAD}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } //END OF STUPID RADEON HACK } #define MAP_RECT_TO_TARGET(_RECT_) \ if (blitInTempPixmap) _RECT_.translate(-temp_visibleRect.topLeft()); else _RECT_ = mapToScreen(mask, data, _RECT_) //BEGIN deco preparations bool noBorder = true; xcb_render_picture_t left = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t top = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t right = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t bottom = XCB_RENDER_PICTURE_NONE; QRect dtr, dlr, drr, dbr; const SceneXRenderDecorationRenderer *renderer = nullptr; if (client) { if (client && !client->noBorder()) { if (client->isDecorated()) { SceneXRenderDecorationRenderer *r = static_cast(client->decoratedClient()->renderer()); if (r) { r->render(); renderer = r; } } noBorder = client->noBorder(); client->layoutDecorationRects(dlr, dtr, drr, dbr); } } if (deleted && !deleted->noBorder()) { renderer = static_cast(deleted->decorationRenderer()); noBorder = deleted->noBorder(); deleted->layoutDecorationRects(dlr, dtr, drr, dbr); } if (renderer) { left = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Left); top = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Top); right = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Right); bottom = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Bottom); } if (!noBorder) { MAP_RECT_TO_TARGET(dtr); MAP_RECT_TO_TARGET(dlr); MAP_RECT_TO_TARGET(drr); MAP_RECT_TO_TARGET(dbr); } //END deco preparations //BEGIN shadow preparations QRect stlr, str, strr, srr, sbrr, sbr, sblr, slr; SceneXRenderShadow* m_xrenderShadow = static_cast(m_shadow); if (wantShadow) { m_xrenderShadow->layoutShadowRects(str, strr, srr, sbrr, sbr, sblr, slr, stlr); MAP_RECT_TO_TARGET(stlr); MAP_RECT_TO_TARGET(str); MAP_RECT_TO_TARGET(strr); MAP_RECT_TO_TARGET(srr); MAP_RECT_TO_TARGET(sbrr); MAP_RECT_TO_TARGET(sbr); MAP_RECT_TO_TARGET(sblr); MAP_RECT_TO_TARGET(slr); } //BEGIN end preparations //BEGIN client preparations QRect dr = cr; if (blitInTempPixmap) { dr.translate(-temp_visibleRect.topLeft()); } else { dr = mapToScreen(mask, data, dr); // Destination rect if (scaled) { cr.moveLeft(cr.x() * xscale); cr.moveTop(cr.y() * yscale); } } const int clientRenderOp = (opaque || blitInTempPixmap) ? XCB_RENDER_PICT_OP_SRC : XCB_RENDER_PICT_OP_OVER; //END client preparations #undef MAP_RECT_TO_TARGET for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { #define RENDER_SHADOW_TILE(_TILE_, _RECT_) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, m_xrenderShadow->picture(SceneXRenderShadow::ShadowElement##_TILE_), \ shadowAlpha, renderTarget, 0, 0, 0, 0, _RECT_.x(), _RECT_.y(), _RECT_.width(), _RECT_.height()) //shadow if (wantShadow) { xcb_render_picture_t shadowAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { shadowAlpha = xRenderBlendPicture(data.opacity()); } RENDER_SHADOW_TILE(TopLeft, stlr); RENDER_SHADOW_TILE(Top, str); RENDER_SHADOW_TILE(TopRight, strr); RENDER_SHADOW_TILE(Left, slr); RENDER_SHADOW_TILE(Right, srr); RENDER_SHADOW_TILE(BottomLeft, sblr); RENDER_SHADOW_TILE(Bottom, sbr); RENDER_SHADOW_TILE(BottomRight, sbrr); } #undef RENDER_SHADOW_TILE // Paint the window contents if (!(client && client->isShade())) { xcb_render_picture_t clientAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { clientAlpha = xRenderBlendPicture(data.opacity()); } xcb_render_composite(connection(), clientRenderOp, pic, clientAlpha, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (data.crossFadeProgress() < 1.0 && data.crossFadeProgress() > 0.0) { XRenderWindowPixmap *previous = previousWindowPixmap(); if (previous && previous != pixmap) { static xcb_render_color_t cFadeColor = {0, 0, 0, 0}; cFadeColor.alpha = uint16_t((1.0 - data.crossFadeProgress()) * 0xffff); if (!s_fadeAlphaPicture) { s_fadeAlphaPicture = new XRenderPicture(xRenderFill(cFadeColor)); } else { xcb_rectangle_t rect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_fadeAlphaPicture, cFadeColor , 1, &rect); } if (previous->size() != pixmap->size()) { xcb_render_transform_t xform2 = { DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix11) * previous->size().width() / pixmap->size().width()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix22) * previous->size().height() / pixmap->size().height()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; xcb_render_set_picture_transform(connection(), previous->picture(), xform2); } xcb_render_composite(connection(), opaque ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_ATOP, previous->picture(), *s_fadeAlphaPicture, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (previous->size() != pixmap->size()) { xcb_render_set_picture_transform(connection(), previous->picture(), identity); } } } if (!opaque) transformed_shape = QRegion(); } if (client || deleted) { if (!noBorder) { xcb_render_picture_t decorationAlpha = xRenderBlendPicture(data.opacity()); auto renderDeco = [decorationAlpha, renderTarget](xcb_render_picture_t deco, const QRect &rect) { if (deco == XCB_RENDER_PICTURE_NONE) { return; } xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, deco, decorationAlpha, renderTarget, 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height()); }; renderDeco(top, dtr); renderDeco(left, dlr); renderDeco(right, drr); renderDeco(bottom, dbr); } } if (data.brightness() != 1.0) { // fake brightness change by overlaying black const float alpha = (1 - data.brightness()) * data.opacity(); xcb_rectangle_t rect; if (blitInTempPixmap) { rect.x = -temp_visibleRect.left(); rect.y = -temp_visibleRect.top(); rect.width = width(); rect.height = height(); } else { rect.x = wr.x(); rect.y = wr.y(); rect.width = wr.width(); rect.height = wr.height(); } xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, renderTarget, preMultiply(data.brightness() < 1.0 ? QColor(0,0,0,255*alpha) : QColor(255,255,255,-alpha*255)), 1, &rect); } if (blitInTempPixmap) { const QRect r = mapToScreen(mask, data, temp_visibleRect); xcb_render_set_picture_transform(connection(), *s_tempPicture, xform); setPictureFilter(*s_tempPicture, filter); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_tempPicture, XCB_RENDER_PICTURE_NONE, m_scene->xrenderBufferPicture(), 0, 0, 0, 0, r.x(), r.y(), r.width(), r.height()); xcb_render_set_picture_transform(connection(), *s_tempPicture, identity); } } if (scaled && !blitInTempPixmap) { xcb_render_set_picture_transform(connection(), pic, identity); if (filter == ImageFilterGood) setPictureFilter(pic, KWin::Scene::ImageFilterFast); if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_NONE}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } } if (xRenderOffscreen()) scene_setXRenderOffscreenTarget(*s_tempPicture); } void SceneXrender::Window::setPictureFilter(xcb_render_picture_t pic, Scene::ImageFilterType filter) { QByteArray filterName; switch (filter) { case KWin::Scene::ImageFilterFast: filterName = QByteArray("fast"); break; case KWin::Scene::ImageFilterGood: filterName = QByteArray("good"); break; } xcb_render_set_picture_filter(connection(), pic, filterName.length(), filterName.constData(), 0, NULL); } WindowPixmap* SceneXrender::Window::createWindowPixmap() { return new XRenderWindowPixmap(this, format); } void SceneXrender::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } //**************************************** // XRenderWindowPixmap //**************************************** XRenderWindowPixmap::XRenderWindowPixmap(Scene::Window *window, xcb_render_pictformat_t format) : WindowPixmap(window) , m_picture(XCB_RENDER_PICTURE_NONE) , m_format(format) { } XRenderWindowPixmap::~XRenderWindowPixmap() { if (m_picture != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_picture); } } void XRenderWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } m_picture = xcb_generate_id(connection()); xcb_render_create_picture(connection(), m_picture, pixmap(), m_format, 0, NULL); } //**************************************** // SceneXrender::EffectFrame //**************************************** XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = NULL; SceneXrender::EffectFrame::EffectFrame(EffectFrameImpl* frame) : Scene::EffectFrame(frame) { m_picture = NULL; m_textPicture = NULL; m_iconPicture = NULL; m_selectionPicture = NULL; } SceneXrender::EffectFrame::~EffectFrame() { delete m_picture; delete m_textPicture; delete m_iconPicture; delete m_selectionPicture; } void SceneXrender::EffectFrame::cleanup() { delete s_effectFrameCircle; s_effectFrameCircle = NULL; } void SceneXrender::EffectFrame::free() { delete m_picture; m_picture = NULL; delete m_textPicture; m_textPicture = NULL; delete m_iconPicture; m_iconPicture = NULL; delete m_selectionPicture; m_selectionPicture = NULL; } void SceneXrender::EffectFrame::freeIconFrame() { delete m_iconPicture; m_iconPicture = NULL; } void SceneXrender::EffectFrame::freeTextFrame() { delete m_textPicture; m_textPicture = NULL; } void SceneXrender::EffectFrame::freeSelection() { delete m_selectionPicture; m_selectionPicture = NULL; } void SceneXrender::EffectFrame::crossFadeIcon() { // TODO: implement me } void SceneXrender::EffectFrame::crossFadeText() { // TODO: implement me } void SceneXrender::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { renderUnstyled(effects->xrenderBufferPicture(), m_effectFrame->geometry(), opacity * frameOpacity); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_picture) { // Lazy creation updatePicture(); } if (m_picture) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_picture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } if (!m_effectFrame->selection().isNull()) { if (!m_selectionPicture) { // Lazy creation const QPixmap pix = m_effectFrame->selectionFrame().framePixmap(); if (!pix.isNull()) // don't try if there's no content m_selectionPicture = new XRenderPicture(m_effectFrame->selectionFrame().framePixmap().toImage()); } if (m_selectionPicture) { const QRect geom = m_effectFrame->selection(); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_selectionPicture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } XRenderPicture fill = xRenderBlendPicture(opacity); // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); if (!m_iconPicture) // lazy creation m_iconPicture = new XRenderPicture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize()).toImage()); QRect geom = QRect(topLeft, m_effectFrame->iconSize()); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_iconPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } // Render text if (!m_effectFrame->text().isEmpty()) { if (!m_textPicture) { // Lazy creation updateTextPicture(); } if (m_textPicture) { xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_textPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, m_effectFrame->geometry().x(), m_effectFrame->geometry().y(), m_effectFrame->geometry().width(), m_effectFrame->geometry().height()); } } } void SceneXrender::EffectFrame::renderUnstyled(xcb_render_picture_t pict, const QRect &rect, qreal opacity) { const int roundness = 5; const QRect area = rect.adjusted(-roundness, -roundness, roundness, roundness); xcb_rectangle_t rects[3]; // center rects[0].x = area.left(); rects[0].y = area.top() + roundness; rects[0].width = area.width(); rects[0].height = area.height() - roundness * 2; // top rects[1].x = area.left() + roundness; rects[1].y = area.top(); rects[1].width = area.width() - roundness * 2; rects[1].height = roundness; // bottom rects[2].x = area.left() + roundness; rects[2].y = area.top() + area.height() - roundness; rects[2].width = area.width() - roundness * 2; rects[2].height = roundness; xcb_render_color_t color = {0, 0, 0, uint16_t(opacity * 0xffff)}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, pict, color, 3, rects); if (!s_effectFrameCircle) { // create the circle const int diameter = roundness * 2; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), diameter, diameter); s_effectFrameCircle = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); // clear it with transparent xcb_rectangle_t xrect = {0, 0, diameter, diameter}; xcb_render_color_t tranparent = {0, 0, 0, 0}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_effectFrameCircle, tranparent, 1, &xrect); static const int num_segments = 80; static const qreal theta = 2 * M_PI / qreal(num_segments); static const qreal c = qCos(theta); //precalculate the sine and cosine static const qreal s = qSin(theta); qreal t; qreal x = roundness;//we start at angle = 0 qreal y = 0; QVector points; xcb_render_pointfix_t point; point.x = DOUBLE_TO_FIXED(roundness); point.y = DOUBLE_TO_FIXED(roundness); points << point; for (int ii = 0; ii <= num_segments; ++ii) { point.x = DOUBLE_TO_FIXED(x + roundness); point.y = DOUBLE_TO_FIXED(y + roundness); points << point; //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } XRenderPicture fill = xRenderFill(Qt::black); xcb_render_tri_fan(connection(), XCB_RENDER_PICT_OP_OVER, fill, *s_effectFrameCircle, 0, 0, 0, points.count(), points.constData()); } // TODO: merge alpha mask with SceneXrender::Window::alphaMask // alpha mask xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 8, pix, rootWindow(), 1, 1); XRenderPicture alphaMask(pix, 8); xcb_free_pixmap(connection(), pix); const uint32_t values[] = {true}; xcb_render_change_picture(connection(), alphaMask, XCB_RENDER_CP_REPEAT, values); color.alpha = int(opacity * 0xffff); xcb_rectangle_t xrect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, alphaMask, color, 1, &xrect); // TODO: replace by lambda #define RENDER_CIRCLE(srcX, srcY, destX, destY) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_effectFrameCircle, alphaMask, \ pict, srcX, srcY, 0, 0, destX, destY, roundness, roundness) RENDER_CIRCLE(0, 0, area.left(), area.top()); RENDER_CIRCLE(0, roundness, area.left(), area.top() + area.height() - roundness); RENDER_CIRCLE(roundness, 0, area.left() + area.width() - roundness, area.top()); RENDER_CIRCLE(roundness, roundness, area.left() + area.width() - roundness, area.top() + area.height() - roundness); #undef RENDER_CIRCLE } void SceneXrender::EffectFrame::updatePicture() { delete m_picture; m_picture = 0L; if (m_effectFrame->style() == EffectFrameStyled) { const QPixmap pix = m_effectFrame->frame().framePixmap(); if (!pix.isNull()) m_picture = new XRenderPicture(pix.toImage()); } } void SceneXrender::EffectFrame::updateTextPicture() { // Mostly copied from SceneOpenGL::EffectFrame::updateTextTexture() above delete m_textPicture; m_textPicture = 0L; if (m_effectFrame->text().isEmpty()) { return; } // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } QPixmap pixmap(m_effectFrame->geometry().size()); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { p.setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); } p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textPicture = new XRenderPicture(pixmap.toImage()); } SceneXRenderShadow::SceneXRenderShadow(Toplevel *toplevel) :Shadow(toplevel) { for (int i=0; iclient(), static_cast(&AbstractClient::addRepaint)); for (int i = 0; i < int(DecorationPart::Count); ++i) { m_pixmaps[i] = XCB_PIXMAP_NONE; m_pictures[i] = nullptr; } } SceneXRenderDecorationRenderer::~SceneXRenderDecorationRenderer() { for (int i = 0; i < int(DecorationPart::Count); ++i) { if (m_pixmaps[i] != XCB_PIXMAP_NONE) { xcb_free_pixmap(connection(), m_pixmaps[i]); } delete m_pictures[i]; } if (m_gc != 0) { xcb_free_gc(connection(), m_gc); } } void SceneXRenderDecorationRenderer::render() { QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizePixmaps(); resetImageSizesDirty(); scheduled = client()->client()->decorationRect(); } const QRect top(QPoint(0, 0), m_sizes[int(DecorationPart::Top)]); const QRect left(QPoint(0, top.height()), m_sizes[int(DecorationPart::Left)]); const QRect right(QPoint(top.width() - m_sizes[int(DecorationPart::Right)].width(), top.height()), m_sizes[int(DecorationPart::Right)]); const QRect bottom(QPoint(0, left.y() + left.height()), m_sizes[int(DecorationPart::Bottom)]); xcb_connection_t *c = connection(); if (m_gc == 0) { m_gc = xcb_generate_id(connection()); xcb_create_gc(c, m_gc, m_pixmaps[int(DecorationPart::Top)], 0, nullptr); } auto renderPart = [this, c](const QRect &geo, const QPoint &offset, int index) { if (geo.isNull()) { return; } QImage image = renderToImage(geo); + Q_ASSERT(image.devicePixelRatio() == 1); xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[index], m_gc, image.width(), image.height(), geo.x() - offset.x(), geo.y() - offset.y(), 0, 32, image.byteCount(), image.constBits()); }; const QRect geometry = scheduled.boundingRect(); renderPart(left.intersected(geometry), left.topLeft(), int(DecorationPart::Left)); renderPart(top.intersected(geometry), top.topLeft(), int(DecorationPart::Top)); renderPart(right.intersected(geometry), right.topLeft(), int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom.topLeft(), int(DecorationPart::Bottom)); xcb_flush(c); } void SceneXRenderDecorationRenderer::resizePixmaps() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); xcb_connection_t *c = connection(); auto checkAndCreate = [this, c](int border, const QRect &rect) { const QSize size = rect.size(); if (m_sizes[border] != size) { m_sizes[border] = size; if (m_pixmaps[border] != XCB_PIXMAP_NONE) { xcb_free_pixmap(c, m_pixmaps[border]); } delete m_pictures[border]; if (!size.isEmpty()) { m_pixmaps[border] = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, m_pixmaps[border], rootWindow(), size.width(), size.height()); m_pictures[border] = new XRenderPicture(m_pixmaps[border], 32); } else { m_pixmaps[border] = XCB_PIXMAP_NONE; m_pictures[border] = nullptr; } } if (!m_pictures[border]) { return; } // fill transparent xcb_rectangle_t r = {0, 0, uint16_t(size.width()), uint16_t(size.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *m_pictures[border], preMultiply(Qt::transparent), 1, &r); }; checkAndCreate(int(DecorationPart::Left), left); checkAndCreate(int(DecorationPart::Top), top); checkAndCreate(int(DecorationPart::Right), right); checkAndCreate(int(DecorationPart::Bottom), bottom); } xcb_render_picture_t SceneXRenderDecorationRenderer::picture(SceneXRenderDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); XRenderPicture *picture = m_pictures[int(part)]; if (!picture) { return XCB_RENDER_PICTURE_NONE; } return *picture; } void SceneXRenderDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } #undef DOUBLE_TO_FIXED #undef FIXED_TO_DOUBLE XRenderFactory::XRenderFactory(QObject *parent) : SceneFactory(parent) { } XRenderFactory::~XRenderFactory() = default; Scene *XRenderFactory::create(QObject *parent) const { auto s = SceneXrender::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // namespace #endif void KWin::SceneXrender::paintCursor() { } diff --git a/scene.cpp b/scene.cpp index 36a459ad6..a406a3ffc 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,1146 +1,1148 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ /* The base class for compositing, implementing shared functionality between the OpenGL and XRender backends. Design: When compositing is turned on, XComposite extension is used to redirect drawing of windows to pixmaps and XDamage extension is used to get informed about damage (changes) to window contents. This code is mostly in composite.cpp . Compositor::performCompositing() starts one painting pass. Painting is done by painting the screen, which in turn paints every window. Painting can be affected using effects, which are chained. E.g. painting a screen means that actually paintScreen() of the first effect is called, which possibly does modifications and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen() is called. There are 3 phases of every paint (not necessarily done together): The pre-paint phase, the paint phase and the post-paint phase. The pre-paint phase is used to find out about how the painting will be actually done (i.e. what the effects will do). For example when only a part of the screen needs to be updated and no effect will do any transformation it is possible to use an optimized paint function. How the painting will be done is controlled by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h . For example an effect that decides to paint a normal windows as translucent will need to modify the mask in its prePaintWindow() to include the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get the mask with this flag turned on and will also paint using transparency. The paint pass does the actual painting, based on the information collected using the pre-paint pass. After running through the effects' paintScreen() either paintGenericScreen() or optimized paintSimpleScreen() are called. Those call paintWindow() on windows (not necessarily all), possibly using clipping to optimize performance and calling paintWindow() first with only PAINT_WINDOW_OPAQUE to paint the opaque parts and then later with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function paintWindow() again goes through effects' paintWindow() until finalPaintWindow() is called, which calls the window's performPaint() to do the actual painting. The post-paint can be used for cleanups and is also used for scheduling repaints during the next painting pass for animations. Effects wanting to repaint certain parts can manually damage them during post-paint and repaint of these parts will be done during the next paint pass. */ #include "scene.h" #include #include #include "client.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "screens.h" #include "shadow.h" #include "wayland_server.h" #include "thumbnailitem.h" #include #include #include namespace KWin { //**************************************** // Scene //**************************************** Scene::Scene(QObject *parent) : QObject(parent) { last_time.invalidate(); // Initialize the timer } Scene::~Scene() { foreach (Window *w, m_windows) { delete w; } } // returns mask and possibly modified region void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION; updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); QRegion region = damage; ScreenPrePaintData pdata; pdata.mask = *mask; pdata.paint = region; effects->prePaintScreen(pdata, time_diff); *mask = pdata.mask; region = pdata.paint; if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { // Region painting is not possible with transformations, // because screen damage doesn't match transformed positions. *mask &= ~PAINT_SCREEN_REGION; region = infiniteRegion(); } else if (*mask & PAINT_SCREEN_REGION) { // make sure not to go outside visible screen region &= displayRegion; } else { // whole screen, not transformed, force region to be full region = displayRegion; } painted_region = region; repaint_region = repaint; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { paintBackground(region); } ScreenPaintData data(projection, outputGeometry); effects->paintScreen(*mask, region, data); foreach (Window *w, stacking_order) { effects->postPaintWindow(effectWindow(w)); } effects->postPaintScreen(); // make sure not to go outside of the screen area *updateRegion = damaged_region; *validRegion = (region | painted_region) & displayRegion; repaint_region = QRegion(); damaged_region = QRegion(); // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } // Compute time since the last painting pass. void Scene::updateTimeDiff() { if (!last_time.isValid()) { // Painting has been idle (optimized out) for some time, // which means time_diff would be huge and would break animations. // Simply set it to one (zero would mean no change at all and could // cause problems). time_diff = 1; last_time.start(); } else time_diff = last_time.restart(); if (time_diff < 0) // check time rollback time_diff = 1; } // Painting pass is optimized away. void Scene::idle() { // Don't break time since last paint for the next pass. last_time.invalidate(); } // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, QRegion region, ScreenPaintData& data) { if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) paintGenericScreen(mask, data); else paintSimpleScreen(mask, region); } // The generic painting code that can handle even transformations. // It simply paints bottom-to-top. void Scene::paintGenericScreen(int orig_mask, ScreenPaintData) { if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintBackground(infiniteRegion()); } QList< Phase2Data > phase2; foreach (Window * w, stacking_order) { // bottom to top Toplevel* topw = w->window(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = infiniteRegion(); // no clipping, so doesn't really matter data.clip = QRegion(); data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } phase2.append(Phase2Data(w, infiniteRegion(), data.clip, data.mask, data.quads)); } foreach (const Phase2Data & d, phase2) { paintWindow(d.window, d.mask, d.region, d.quads); } const QSize &screenSize = screens()->size(); damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height()); } // The optimized case without any transformations at all. // It can paint only the requested region and can use clipping // to reduce painting and improve performance. void Scene::paintSimpleScreen(int orig_mask, QRegion region) { assert((orig_mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0); QList< QPair< Window*, Phase2Data > > phase2data; QRegion dirtyArea = region; bool opaqueFullscreen(false); for (int i = 0; // do prePaintWindow bottom to top i < stacking_order.count(); ++i) { Window* w = stacking_order[ i ]; Toplevel* topw = w->window(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = region; data.paint |= topw->repaints(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) if (w->isOpaque()) { AbstractClient *c = dynamic_cast(topw); if (c) { opaqueFullscreen = c->isFullScreen(); } Client *cc = dynamic_cast(c); // the window is fully opaque if (cc && cc->decorationHasAlpha()) { // decoration uses alpha channel, so we may not exclude it in clipping data.clip = w->clientShape().translated(w->x(), w->y()); } else { // decoration is fully opaque if (c && c->isShade()) { data.clip = QRegion(); } else { data.clip = w->shape().translated(w->x(), w->y()); } } } else if (topw->hasAlpha() && topw->opacity() == 1.0) { // the window is partially opaque data.clip = (w->clientShape() & topw->opaqueRegion().translated(topw->clientPos())).translated(w->x(), w->y()); } else { data.clip = QRegion(); } data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } dirtyArea |= data.paint; // Schedule the window for painting phase2data.append(QPair< Window*, Phase2Data >(w,Phase2Data(w, data.paint, data.clip, data.mask, data.quads))); } // Save the part of the repaint region that's exclusively rendered to // bring a reused back buffer up to date. Then union the dirty region // with the repaint region. const QRegion repaintClip = repaint_region - dirtyArea; dirtyArea |= repaint_region; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations if (!fullRepaint) { extendPaintRegion(dirtyArea, opaqueFullscreen); fullRepaint = (dirtyArea == displayRegion); } QRegion allclips, upperTranslucentDamage; upperTranslucentDamage = repaint_region; // This is the occlusion culling pass for (int i = phase2data.count() - 1; i >= 0; --i) { QPair< Window*, Phase2Data > *entry = &phase2data[i]; Phase2Data *data = &entry->second; if (fullRepaint) data->region = displayRegion; else data->region |= upperTranslucentDamage; // subtract the parts which will possibly been drawn as part of // a higher opaque window data->region -= allclips; // Here we rely on WindowPrePaintData::setTranslucent() to remove // the clip if needed. if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSFORMED)) { // clip away the opaque regions for all windows below this one allclips |= data->clip; // extend the translucent damage for windows below this by remaining (translucent) regions if (!fullRepaint) upperTranslucentDamage |= data->region - data->clip; } else if (!fullRepaint) { upperTranslucentDamage |= data->region; } } QRegion paintedArea; // Fill any areas of the root window not covered by opaque windows if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintedArea = dirtyArea - allclips; paintBackground(paintedArea); } // Now walk the list bottom to top and draw the windows. for (int i = 0; i < phase2data.count(); ++i) { Phase2Data *data = &phase2data[i].second; // add all regions which have been drawn so far paintedArea |= data->region; data->region = paintedArea; paintWindow(data->window, data->mask, data->region, data->quads); } if (fullRepaint) { painted_region = displayRegion; damaged_region = displayRegion; } else { painted_region |= paintedArea; // Clip the repainted region from the damaged region. // It's important that we don't add the union of the damaged region // and the repainted region to the damage history. Otherwise the // repaint region will grow with every frame until it eventually // covers the whole back buffer, at which point we're always doing // full repaints. damaged_region = paintedArea - repaintClip; } } void Scene::windowAdded(Toplevel *c) { assert(!m_windows.contains(c)); Scene::Window *w = createWindow(c); m_windows[ c ] = w; connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(windowGeometryShapeChanged(KWin::Toplevel*))); connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*))); //A change of scale won't affect the geometry in compositor co-ordinates, but will affect the window quads. if (c->surface()) { connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, std::bind(&Scene::windowGeometryShapeChanged, this, c)); } + connect(c, &Toplevel::screenScaleChanged, std::bind(&Scene::windowGeometryShapeChanged, this, c)); c->effectWindow()->setSceneWindow(w); c->getShadow(); w->updateShadow(c->shadow()); } void Scene::windowClosed(Toplevel *c, Deleted *deleted) { assert(m_windows.contains(c)); if (deleted != NULL) { // replace c with deleted Window* w = m_windows.take(c); w->updateToplevel(deleted); if (w->shadow()) { w->shadow()->setToplevel(deleted); } m_windows[ deleted ] = w; } else { delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } } void Scene::windowDeleted(Deleted *c) { assert(m_windows.contains(c)); delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } void Scene::windowGeometryShapeChanged(Toplevel *c) { if (!m_windows.contains(c)) // this is ok, shape is not valid by default return; Window *w = m_windows[ c ]; w->discardShape(); } void Scene::createStackingOrder(ToplevelList toplevels) { // TODO: cache the stacking_order in case it has not changed foreach (Toplevel *c, toplevels) { assert(m_windows.contains(c)); stacking_order.append(m_windows[ c ]); } } void Scene::clearStackingOrder() { stacking_order.clear(); } static Scene::Window *s_recursionCheck = NULL; void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) { // no painting outside visible screen (and no transformations) const QSize &screenSize = screens()->size(); region &= QRect(0, 0, screenSize.width(), screenSize.height()); if (region.isEmpty()) // completely clipped return; if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) { // should not get painted return; } if (s_recursionCheck == w) { return; } WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); data.quads = quads; effects->paintWindow(effectWindow(w), mask, region, data); // paint thumbnails on top of window paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); // and desktop thumbnails paintDesktopThumbnails(w); } static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) { if (item->clip() && item->clipTo()) { // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine // that's why we have to get the offset by inspecting the anchors properties QQuickItem *parentItem = item->clipTo(); QPointF offset; QVariant anchors = parentItem->property("anchors"); if (anchors.isValid()) { if (QObject *anchorsObject = anchors.value()) { offset.setX(anchorsObject->property("leftMargin").toReal()); offset.setY(anchorsObject->property("topMargin").toReal()); } } QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); if (QQuickItem *p = parentItem->parentItem()) { rect = p->mapRectToScene(rect); } clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); } } void Scene::paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); it != wImpl->thumbnails().constEnd(); ++it) { if (it.value().isNull()) { continue; } WindowThumbnailItem *item = it.key(); if (!item->isVisible()) { continue; } EffectWindowImpl *thumb = it.value().data(); WindowPaintData thumbData(thumb, screenProjectionMatrix()); thumbData.setOpacity(opacity); thumbData.setBrightness(brightness * item->brightness()); thumbData.setSaturation(saturation * item->saturation()); const QRect visualThumbRect(thumb->expandedGeometry()); QSizeF size = QSizeF(visualThumbRect.size()); size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { size = QSizeF(visualThumbRect.size()); } thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); if (!item->window()) { continue; } const QPointF point = item->mapToScene(item->position()); qreal x = point.x() + w->x() + (item->width() - size.width())/2; qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; x -= thumb->x(); y -= thumb->y(); // compensate shadow topleft padding x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); thumbData.setXTranslation(x); thumbData.setYTranslation(y); int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; if (thumbData.opacity() == 1.0) { thumbMask |= PAINT_WINDOW_OPAQUE; } else { thumbMask |= PAINT_WINDOW_TRANSLUCENT; } QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); } } void Scene::paintDesktopThumbnails(Scene::Window *w) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); it != wImpl->desktopThumbnails().constEnd(); ++it) { DesktopThumbnailItem *item = *it; if (!item->isVisible()) { continue; } if (!item->window()) { continue; } s_recursionCheck = w; ScreenPaintData data; const QSize &screenSize = screens()->size(); QSize size = screenSize; size.scale(item->width(), item->height(), Qt::KeepAspectRatio); data *= QVector2D(size.width() / double(screenSize.width()), size.height() / double(screenSize.height())); const QPointF point = item->mapToScene(item->position()); const qreal x = point.x() + w->x() + (item->width() - size.width())/2; const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; const QRect region = QRect(x, y, item->width(), item->height()); QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); data += QPointF(x, y); const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; paintDesktop(item->desktop(), desktopMask, clippingRegion, data); s_recursionCheck = NULL; } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { static_cast(effects)->paintDesktop(desktop, mask, region, data); } // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } // will be eventually called from drawWindow() void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } w->sceneWindow()->performPaint(mask, region, data); } void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { Q_UNUSED(region); Q_UNUSED(opaqueFullscreen); } bool Scene::blocksForRetrace() const { return false; } bool Scene::syncsToVBlank() const { return false; } void Scene::screenGeometryChanged(const QSize &size) { if (!overlayWindow()) { return; } overlayWindow()->resize(size); } bool Scene::makeOpenGLContextCurrent() { return false; } void Scene::doneOpenGLContextCurrent() { } void Scene::triggerFence() { } QMatrix4x4 Scene::screenProjectionMatrix() const { return QMatrix4x4(); } xcb_render_picture_t Scene::xrenderBufferPicture() const { return XCB_RENDER_PICTURE_NONE; } QPainter *Scene::scenePainter() const { return nullptr; } QImage *Scene::qpainterRenderBuffer() const { return nullptr; } QVector Scene::openGLPlatformInterfaceExtensions() const { return QVector{}; } //**************************************** // Scene::Window //**************************************** Scene::Window::Window(Toplevel * c) : toplevel(c) , filter(ImageFilterFast) , m_shadow(NULL) , m_currentPixmap() , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) , shape_valid(false) , cached_quad_list(NULL) { } Scene::Window::~Window() { delete m_shadow; } void Scene::Window::referencePreviousPixmap() { if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) { m_referencePixmapCounter++; } } void Scene::Window::unreferencePreviousPixmap() { if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) { return; } m_referencePixmapCounter--; if (m_referencePixmapCounter == 0) { m_previousPixmap.reset(); } } void Scene::Window::pixmapDiscarded() { if (!m_currentPixmap.isNull()) { if (m_currentPixmap->isValid()) { m_previousPixmap.reset(m_currentPixmap.take()); m_previousPixmap->markAsDiscarded(); } else { m_currentPixmap.reset(); } } } void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag shape_valid = false; cached_quad_list.reset(); } // Find out the shape of the window using the XShape extension // or if shape is not set then simply it's the window geometry. const QRegion &Scene::Window::shape() const { if (!shape_valid) { if (toplevel->shape()) { auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); if (!reply.isNull()) { shape_region = QRegion(); auto *rects = xcb_shape_get_rectangles_rectangles(reply.data()); for (int i = 0; i < xcb_shape_get_rectangles_rectangles_length(reply.data()); ++i) shape_region += QRegion(rects[ i ].x, rects[ i ].y, rects[ i ].width, rects[ i ].height); // make sure the shape is sane (X is async, maybe even XShape is broken) shape_region &= QRegion(0, 0, width(), height()); } else shape_region = QRegion(); } else shape_region = QRegion(0, 0, width(), height()); shape_valid = true; } return shape_region; } QRegion Scene::Window::clientShape() const { if (AbstractClient *c = dynamic_cast< AbstractClient * > (toplevel)) { if (c->isShade()) return QRegion(); } // TODO: cache const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); return r.isEmpty() ? QRegion() : r; } bool Scene::Window::isVisible() const { if (toplevel->isDeleted()) return false; if (!toplevel->isOnCurrentDesktop()) return false; if (!toplevel->isOnCurrentActivity()) return false; if (AbstractClient *c = dynamic_cast(toplevel)) return c->isShown(true); return true; // Unmanaged is always visible } bool Scene::Window::isOpaque() const { return toplevel->opacity() == 1.0 && !toplevel->hasAlpha(); } bool Scene::Window::isPaintingEnabled() const { return !disable_painting; } void Scene::Window::resetPaintingEnabled() { disable_painting = 0; if (toplevel->isDeleted()) disable_painting |= PAINT_DISABLED_BY_DELETE; if (static_cast(effects)->isDesktopRendering()) { if (!toplevel->isOnDesktop(static_cast(effects)->currentRenderedDesktop())) { disable_painting |= PAINT_DISABLED_BY_DESKTOP; } } else { if (!toplevel->isOnCurrentDesktop()) disable_painting |= PAINT_DISABLED_BY_DESKTOP; } if (!toplevel->isOnCurrentActivity()) disable_painting |= PAINT_DISABLED_BY_ACTIVITY; if (AbstractClient *c = dynamic_cast(toplevel)) { if (c->isMinimized()) disable_painting |= PAINT_DISABLED_BY_MINIMIZE; if (c->tabGroup() && c != c->tabGroup()->current()) disable_painting |= PAINT_DISABLED_BY_TAB_GROUP; if (c->isHiddenInternal()) { disable_painting |= PAINT_DISABLED; } } } void Scene::Window::enablePainting(int reason) { disable_painting &= ~reason; } void Scene::Window::disablePainting(int reason) { disable_painting |= reason; } WindowQuadList Scene::Window::buildQuads(bool force) const { if (cached_quad_list != NULL && !force) return *cached_quad_list; WindowQuadList ret; qreal scale = 1.0; if (toplevel->surface()) { scale = toplevel->surface()->scale(); } if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->decorationRect().size()) ret = makeQuads(WindowQuadContents, shape(), QPoint(0,0), scale); // has no decoration else { AbstractClient *client = dynamic_cast(toplevel); QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center; + qreal decorationScale = 1.0; ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos(), scale); - QRect rects[4]; bool isShadedClient = false; if (client) { client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); + decorationScale = client->screenScale(); isShadedClient = client->isShade() || center.isEmpty(); } if (isShadedClient) { const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3]; - ret += makeDecorationQuads(rects, bounding); + ret += makeDecorationQuads(rects, bounding, decorationScale); } else { - ret += makeDecorationQuads(rects, decoration); + ret += makeDecorationQuads(rects, decoration, decorationScale); } } if (m_shadow && toplevel->wantsShadowToBeRendered()) { ret << m_shadow->shadowQuads(); } effects->buildQuads(toplevel->effectWindow(), ret); cached_quad_list.reset(new WindowQuadList(ret)); return ret; } -WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion) const +WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale) const { WindowQuadList list; const QPoint offsets[4] = { QPoint(-rects[0].x() + rects[1].height() + rects[3].height() + 2, -rects[0].y()), // Left QPoint(-rects[1].x(), -rects[1].y()), // Top QPoint(-rects[2].x() + rects[1].height() + rects[3].height() + rects[0].width() + 3, -rects[2].y()), // Right QPoint(-rects[3].x(), -rects[3].y() + rects[1].height() + 1) // Bottom }; const Qt::Orientation orientations[4] = { Qt::Vertical, // Left Qt::Horizontal, // Top Qt::Vertical, // Right Qt::Horizontal, // Bottom }; for (int i = 0; i < 4; i++) { foreach (const QRect &r, (region & rects[i]).rects()) { if (!r.isValid()) continue; const bool swap = orientations[i] == Qt::Vertical; const int x0 = r.x(); const int y0 = r.y(); const int x1 = r.x() + r.width(); const int y1 = r.y() + r.height(); - const int u0 = x0 + offsets[i].x(); - const int v0 = y0 + offsets[i].y(); - const int u1 = x1 + offsets[i].x(); - const int v1 = y1 + offsets[i].y(); + const int u0 = (x0 + offsets[i].x()) * textureScale; + const int v0 = (y0 + offsets[i].y()) * textureScale; + const int u1 = (x1 + offsets[i].x()) * textureScale; + const int v1 = (y1 + offsets[i].y()) * textureScale; WindowQuad quad(WindowQuadDecoration); quad.setUVAxisSwapped(swap); if (swap) { quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left } else { quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left } list.append(quad); } } return list; } WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset, qreal scale) const { WindowQuadList ret; foreach (const QRect & r, reg.rects()) { WindowQuad quad(type); // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech quad[ 0 ] = WindowVertex(QPointF(r.x(), r.y()), QPointF(r.x() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 1 ] = WindowVertex(QPointF(r.x() + r.width(), r.y()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 2 ] = WindowVertex(QPointF(r.x() + r.width(), r.y() + r.height()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); quad[ 3 ] = WindowVertex(QPointF(r.x(), r.y() + r.height()), QPointF(r.x() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); ret.append(quad); } return ret; } void Scene::Window::updateShadow(Shadow* shadow) { if (m_shadow == shadow) { return; } delete m_shadow; m_shadow = shadow; } //**************************************** // WindowPixmap //**************************************** WindowPixmap::WindowPixmap(Scene::Window *window) : m_window(window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) { } WindowPixmap::WindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : m_window(parent->m_window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) , m_parent(parent) , m_subSurface(subSurface) { } WindowPixmap::~WindowPixmap() { if (m_pixmap != XCB_WINDOW_NONE) { xcb_free_pixmap(connection(), m_pixmap); } if (m_buffer) { using namespace KWayland::Server; QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } } void WindowPixmap::create() { if (isValid() || toplevel()->isDeleted()) { return; } // always update from Buffer on Wayland, don't try using XPixmap if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer updateBuffer(); if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { m_window->unreferencePreviousPixmap(); } return; } XServerGrabber grabber; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection(), toplevel()->frameId(), pix); Xcb::WindowAttributes windowAttributes(toplevel()->frameId()); Xcb::WindowGeometry windowGeometry(toplevel()->frameId()); if (xcb_generic_error_t *error = xcb_request_check(connection(), namePixmapCookie)) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << error->error_code; free(error); return; } // check that the received pixmap is valid and actually matches what we // know about the window (i.e. size) if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } if (!windowGeometry || windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } m_pixmap = pix; m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) { Q_UNUSED(subSurface) return nullptr; } bool WindowPixmap::isValid() const { if (!m_buffer.isNull() || !m_fbo.isNull()) { return true; } return m_pixmap != XCB_PIXMAP_NONE; } void WindowPixmap::updateBuffer() { using namespace KWayland::Server; if (SurfaceInterface *s = surface()) { QVector oldTree = m_children; QVector children; using namespace KWayland::Server; const auto subSurfaces = s->childSubSurfaces(); for (const auto &subSurface : subSurfaces) { if (subSurface.isNull()) { continue; } auto it = std::find_if(oldTree.begin(), oldTree.end(), [subSurface] (WindowPixmap *p) { return p->m_subSurface == subSurface; }); if (it != oldTree.end()) { children << *it; (*it)->updateBuffer(); oldTree.erase(it); } else { WindowPixmap *p = createChild(subSurface); if (p) { p->create(); children << p; } } } setChildren(children); qDeleteAll(oldTree); if (auto b = s->buffer()) { if (b == m_buffer) { // no change return; } if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } m_buffer = b; m_buffer->ref(); QObject::connect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); } else if (m_subSurface) { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } else { // might be an internal window const auto &fbo = toplevel()->internalFramebufferObject(); if (!fbo.isNull()) { m_fbo = fbo; } } } else { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } } KWayland::Server::SurfaceInterface *WindowPixmap::surface() const { if (!m_subSurface.isNull()) { return m_subSurface->surface().data(); } else { return toplevel()->surface(); } } //**************************************** // Scene::EffectFrame //**************************************** Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame) : m_effectFrame(frame) { } Scene::EffectFrame::~EffectFrame() { } SceneFactory::SceneFactory(QObject *parent) : QObject(parent) { } SceneFactory::~SceneFactory() { } } // namespace diff --git a/scene.h b/scene.h index 8f3b9615f..f7c67c5ca 100644 --- a/scene.h +++ b/scene.h @@ -1,682 +1,682 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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_SCENE_H #define KWIN_SCENE_H #include "toplevel.h" #include "utils.h" #include "kwineffects.h" #include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class BufferInterface; class SubSurfaceInterface; } } namespace KWin { namespace Decoration { class DecoratedClientImpl; class Renderer; } class AbstractThumbnailItem; class Deleted; class EffectFrameImpl; class EffectWindowImpl; class OverlayWindow; class Shadow; class WindowPixmap; // The base class for compositing backends. class KWIN_EXPORT Scene : public QObject { Q_OBJECT public: explicit Scene(QObject *parent = nullptr); virtual ~Scene() = 0; class EffectFrame; class Window; // Returns true if the ctor failed to properly initialize. virtual bool initFailed() const = 0; virtual CompositingType compositingType() const = 0; virtual bool hasPendingFlush() const { return false; } // Repaints the given screen areas, windows provides the stacking order. // The entry point for the main part of the painting pass. // returns the time since the last vblank signal - if there's one // ie. "what of this frame is lost to painting" virtual qint64 paint(QRegion damage, ToplevelList windows) = 0; // Notification function - KWin core informs about changes. // Used to mainly discard cached data. // a new window has been created void windowAdded(Toplevel*); /** * @brief Creates the Scene backend of an EffectFrame. * * @param frame The EffectFrame this Scene::EffectFrame belongs to. */ virtual Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) = 0; /** * @brief Creates the Scene specific Shadow subclass. * * An implementing class has to create a proper instance. It is not allowed to * return @c null. * * @param toplevel The Toplevel for which the Shadow needs to be created. */ virtual Shadow *createShadow(Toplevel *toplevel) = 0; /** * Method invoked when the screen geometry is changed. * Reimplementing classes should also invoke the parent method * as it takes care of resizing the overlay window. * @param size The new screen geometry size **/ virtual void screenGeometryChanged(const QSize &size); // Flags controlling how painting is done. enum { // Window (or at least part of it) will be painted opaque. PAINT_WINDOW_OPAQUE = 1 << 0, // Window (or at least part of it) will be painted translucent. PAINT_WINDOW_TRANSLUCENT = 1 << 1, // Window will be painted with transformed geometry. PAINT_WINDOW_TRANSFORMED = 1 << 2, // Paint only a region of the screen (can be optimized, cannot // be used together with TRANSFORMED flags). PAINT_SCREEN_REGION = 1 << 3, // Whole screen will be painted with transformed geometry. PAINT_SCREEN_TRANSFORMED = 1 << 4, // At least one window will be painted with transformed geometry. PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, // Clear whole background as the very first step, without optimizing it PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been removed // Window will be painted with a lanczos filter. PAINT_WINDOW_LANCZOS = 1 << 8 // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; // types of filtering available enum ImageFilterType { ImageFilterFast, ImageFilterGood }; // there's nothing to paint (adjust time_diff later) virtual void idle(); virtual bool blocksForRetrace() const; virtual bool syncsToVBlank() const; virtual OverlayWindow* overlayWindow() = 0; virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent(); virtual QMatrix4x4 screenProjectionMatrix() const; /** * Whether the Scene uses an X11 overlay window to perform compositing. */ virtual bool usesOverlayWindow() const = 0; virtual void triggerFence(); virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *) = 0; /** * Whether the Scene is able to drive animations. * This is used as a hint to the effects system which effects can be supported. * If the Scene performs software rendering it is supposed to return @c false, * if rendering is hardware accelerated it should return @c true. **/ virtual bool animationsSupported() const = 0; /** * The render buffer used by an XRender based compositor scene. * Default implementation returns XCB_RENDER_PICTURE_NONE **/ virtual xcb_render_picture_t xrenderBufferPicture() const; /** * The QPainter used by a QPainter based compositor scene. * Default implementation returns @c nullptr; **/ virtual QPainter *scenePainter() const; /** * The render buffer used by a QPainter based compositor. * Default implementation returns @c nullptr. **/ virtual QImage *qpainterRenderBuffer() const; /** * The backend specific extensions (e.g. EGL/GLX extensions). * * Not the OpenGL (ES) extension! * * Default implementation returns empty list **/ virtual QVector openGLPlatformInterfaceExtensions() const; Q_SIGNALS: void frameRendered(); void resetCompositing(); public Q_SLOTS: // a window has been destroyed void windowDeleted(KWin::Deleted*); // shape/size of a window changed void windowGeometryShapeChanged(KWin::Toplevel* c); // a window has been closed void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted); protected: virtual Window *createWindow(Toplevel *toplevel) = 0; void createStackingOrder(ToplevelList toplevels); void clearStackingOrder(); // shared implementation, starts painting the screen void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4(), const QRect &outputGeometry = QRect()); // Render cursor texture in case hardware cursor is disabled/non-applicable virtual void paintCursor() = 0; friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, QRegion region, ScreenPaintData& data); // shared implementation of painting the screen in the generic // (unoptimized) way virtual void paintGenericScreen(int mask, ScreenPaintData data); // shared implementation of painting the screen in an optimized way virtual void paintSimpleScreen(int mask, QRegion region); // paint the background (not the desktop background - the whole background) virtual void paintBackground(QRegion region) = 0; // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // shared implementation, starts painting the window virtual void paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); // compute time since the last repaint void updateTimeDiff(); // saved data for 2nd pass of optimized screen painting struct Phase2Data { Phase2Data(Window* w, QRegion r, QRegion c, int m, const WindowQuadList& q) : window(w), region(r), clip(c), mask(m), quads(q) {} Phase2Data() { window = 0; mask = 0; } Window* window; QRegion region; QRegion clip; int mask; WindowQuadList quads; }; // The region which actually has been painted by paintScreen() and should be // copied from the buffer to the screen. I.e. the region returned from Scene::paintScreen(). // Since prePaintWindow() can extend areas to paint, these changes would have to propagate // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather // than propagate them up in arguments. QRegion painted_region; // Additional damage that needs to be repaired to bring a reused back buffer up to date QRegion repaint_region; // The dirty region before it was unioned with repaint_region QRegion damaged_region; // time since last repaint int time_diff; QElapsedTimer last_time; private: void paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation); void paintDesktopThumbnails(Scene::Window *w); QHash< Toplevel*, Window* > m_windows; // windows in their stacking order QVector< Window* > stacking_order; }; /** * Factory class to create a Scene. Needs to be implemented by the plugins. **/ class KWIN_EXPORT SceneFactory : public QObject { Q_OBJECT public: virtual ~SceneFactory(); /** * @returns The created Scene, may be @c nullptr. **/ virtual Scene *create(QObject *parent = nullptr) const = 0; protected: explicit SceneFactory(QObject *parent); }; // The base class for windows representations in composite backends class Scene::Window { public: Window(Toplevel* c); virtual ~Window(); // perform the actual painting of the window virtual void performPaint(int mask, QRegion region, WindowPaintData data) = 0; // do any cleanup needed when the window's composite pixmap is discarded void pixmapDiscarded(); int x() const; int y() const; int width() const; int height() const; QRect geometry() const; QPoint pos() const; QSize size() const; QRect rect() const; // access to the internal window class // TODO eventually get rid of this Toplevel* window() const; // should the window be painted bool isPaintingEnabled() const; void resetPaintingEnabled(); // Flags explaining why painting should be disabled enum { // Window will not be painted PAINT_DISABLED = 1 << 0, // Window will not be painted because it is deleted PAINT_DISABLED_BY_DELETE = 1 << 1, // Window will not be painted because of which desktop it's on PAINT_DISABLED_BY_DESKTOP = 1 << 2, // Window will not be painted because it is minimized PAINT_DISABLED_BY_MINIMIZE = 1 << 3, // Window will not be painted because it is not the active window in a client group PAINT_DISABLED_BY_TAB_GROUP = 1 << 4, // Window will not be painted because it's not on the current activity PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; void enablePainting(int reason); void disablePainting(int reason); // is the window visible at all bool isVisible() const; // is the window fully opaque bool isOpaque() const; // shape of the window const QRegion &shape() const; QRegion clientShape() const; void discardShape(); void updateToplevel(Toplevel* c); // creates initial quad list for the window virtual WindowQuadList buildQuads(bool force = false) const; void updateShadow(Shadow* shadow); const Shadow* shadow() const; Shadow* shadow(); void referencePreviousPixmap(); void unreferencePreviousPixmap(); protected: WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0), qreal textureScale = 1.0) const; - WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion) const; + WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale = 1.0) const; /** * @brief Returns the WindowPixmap for this Window. * * If the WindowPixmap does not yet exist, this method will invoke @link createWindowPixmap. * If the WindowPixmap is not valid it tries to create it, in case this succeeds the WindowPixmap is * returned. In case it fails, the previous (and still valid) WindowPixmap is returned. * * Note: this method can return @c NULL as there might neither be a valid previous nor current WindowPixmap * around. * * The WindowPixmap gets casted to the type passed in as a template parameter. That way this class does not * need to know the actual WindowPixmap subclass used by the concrete Scene implementations. * * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ template T *windowPixmap(); template T *previousWindowPixmap(); /** * @brief Factory method to create a WindowPixmap. * * The inheriting classes need to implement this method to create a new instance of their WindowPixmap subclass. * Note: do not use @link WindowPixmap::create on the created instance. The Scene will take care of that. */ virtual WindowPixmap *createWindowPixmap() = 0; Toplevel* toplevel; ImageFilterType filter; Shadow *m_shadow; private: QScopedPointer m_currentPixmap; QScopedPointer m_previousPixmap; int m_referencePixmapCounter; int disable_painting; mutable QRegion shape_region; mutable bool shape_valid; mutable QScopedPointer cached_quad_list; Q_DISABLE_COPY(Window) }; /** * @brief Wrapper for a pixmap of the @link Scene::Window. * * This class encapsulates the functionality to get the pixmap for a window. When initialized the pixmap is not yet * mapped to the window and @link isValid will return @c false. The pixmap mapping to the window can be established * through @link create. If it succeeds @link isValid will return @c true, otherwise it will keep in the non valid * state and it can be tried to create the pixmap mapping again (e.g. in the next frame). * * This class is not intended to be updated when the pixmap is no longer valid due to e.g. resizing the window. * Instead a new instance of this class should be instantiated. The idea behind this is that a valid pixmap does not * get destroyed, but can continue to be used. To indicate that a newer pixmap should in generally be around, one can * use @link markAsDiscarded. * * This class is intended to be inherited for the needs of the compositor backends which need further mapping from * the native pixmap to the respective rendering format. */ class KWIN_EXPORT WindowPixmap { public: virtual ~WindowPixmap(); /** * @brief Tries to create the mapping between the Window and the pixmap. * * In case this method succeeds in creating the pixmap for the window, @link isValid will return @c true otherwise * @c false. * * Inheriting classes should re-implement this method in case they need to add further functionality for mapping the * native pixmap to the rendering format. */ virtual void create(); /** * @return @c true if the pixmap has been created and is valid, @c false otherwise */ virtual bool isValid() const; /** * @return The native X11 pixmap handle */ xcb_pixmap_t pixmap() const; /** * @return The Wayland BufferInterface for this WindowPixmap. **/ QPointer buffer() const; const QSharedPointer &fbo() const; /** * @brief Whether this WindowPixmap is considered as discarded. This means the window has changed in a way that a new * WindowPixmap should have been created already. * * @return @c true if this WindowPixmap is considered as discarded, @c false otherwise. * @see markAsDiscarded */ bool isDiscarded() const; /** * @brief Marks this WindowPixmap as discarded. From now on @link isDiscarded will return @c true. This method should * only be used by the Window when it changes in a way that a new pixmap is required. * * @see isDiscarded */ void markAsDiscarded(); /** * The size of the pixmap. */ const QSize &size() const; /** * The geometry of the Client's content inside the pixmap. In case of a decorated Client the * pixmap also contains the decoration which is not rendered into this pixmap, though. This * contentsRect tells where inside the complete pixmap the real content is. */ const QRect &contentsRect() const; /** * @brief Returns the Toplevel this WindowPixmap belongs to. * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ Toplevel *toplevel() const; /** * @returns the parent WindowPixmap in the sub-surface tree **/ WindowPixmap *parent() const { return m_parent; } /** * @returns the current sub-surface tree **/ QVector children() const { return m_children; } /** * @returns the subsurface this WindowPixmap is for if it is not for a root window **/ QPointer subSurface() const { return m_subSurface; } /** * @returns the surface this WindowPixmap references, might be @c null. **/ KWayland::Server::SurfaceInterface *surface() const; protected: explicit WindowPixmap(Scene::Window *window); explicit WindowPixmap(const QPointer &subSurface, WindowPixmap *parent); virtual WindowPixmap *createChild(const QPointer &subSurface); /** * @return The Window this WindowPixmap belongs to */ Scene::Window *window(); /** * Should be called by the implementing subclasses when the Wayland Buffer changed and needs * updating. **/ virtual void updateBuffer(); /** * Sets the sub-surface tree to @p children. **/ void setChildren(const QVector &children) { m_children = children; } private: Scene::Window *m_window; xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; QRect m_contentsRect; QPointer m_buffer; QSharedPointer m_fbo; WindowPixmap *m_parent = nullptr; QVector m_children; QPointer m_subSurface; }; class Scene::EffectFrame { public: EffectFrame(EffectFrameImpl* frame); virtual ~EffectFrame(); virtual void render(QRegion region, double opacity, double frameOpacity) = 0; virtual void free() = 0; virtual void freeIconFrame() = 0; virtual void freeTextFrame() = 0; virtual void freeSelection() = 0; virtual void crossFadeIcon() = 0; virtual void crossFadeText() = 0; protected: EffectFrameImpl* m_effectFrame; }; inline int Scene::Window::x() const { return toplevel->x(); } inline int Scene::Window::y() const { return toplevel->y(); } inline int Scene::Window::width() const { return toplevel->width(); } inline int Scene::Window::height() const { return toplevel->height(); } inline QRect Scene::Window::geometry() const { return toplevel->geometry(); } inline QSize Scene::Window::size() const { return toplevel->size(); } inline QPoint Scene::Window::pos() const { return toplevel->pos(); } inline QRect Scene::Window::rect() const { return toplevel->rect(); } inline Toplevel* Scene::Window::window() const { return toplevel; } inline void Scene::Window::updateToplevel(Toplevel* c) { toplevel = c; } inline const Shadow* Scene::Window::shadow() const { return m_shadow; } inline Shadow* Scene::Window::shadow() { return m_shadow; } inline QPointer WindowPixmap::buffer() const { return m_buffer; } inline const QSharedPointer &WindowPixmap::fbo() const { return m_fbo; } template inline T* Scene::Window::windowPixmap() { if (m_currentPixmap.isNull()) { m_currentPixmap.reset(createWindowPixmap()); } if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } m_currentPixmap->create(); if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } else { return static_cast(m_previousPixmap.data()); } } template inline T* Scene::Window::previousWindowPixmap() { return static_cast(m_previousPixmap.data()); } inline Toplevel* WindowPixmap::toplevel() const { return m_window->window(); } inline xcb_pixmap_t WindowPixmap::pixmap() const { return m_pixmap; } inline bool WindowPixmap::isDiscarded() const { return m_discarded; } inline void WindowPixmap::markAsDiscarded() { m_discarded = true; m_window->referencePreviousPixmap(); } inline const QRect &WindowPixmap::contentsRect() const { return m_contentsRect; } inline const QSize &WindowPixmap::size() const { return m_pixmapSize; } } // namespace Q_DECLARE_INTERFACE(KWin::SceneFactory, "org.kde.kwin.Scene") #endif diff --git a/toplevel.cpp b/toplevel.cpp index aec48615c..ab04f6817 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -1,547 +1,557 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 "toplevel.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client.h" #include "client_machine.h" #include "effects.h" #include "screens.h" #include "shadow.h" #include "xcbutils.h" #include #include namespace KWin { Toplevel::Toplevel() : m_visual(XCB_NONE) , bit_depth(24) , info(NULL) , ready_for_painting(true) , m_isDamaged(false) , m_client() , damage_handle(None) , is_shape(false) , effect_window(NULL) , m_clientMachine(new ClientMachine(this)) , wmClientLeaderWin(0) , m_damageReplyPending(false) , m_screen(0) , m_skipCloseAnimation(false) { connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); connect(screens(), SIGNAL(changed()), SLOT(checkScreen())); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(checkScreen())); setupCheckScreenConnection(); } Toplevel::~Toplevel() { assert(damage_handle == None); delete info; } QDebug& operator<<(QDebug& stream, const Toplevel* cl) { if (cl == NULL) return stream << "\'NULL\'"; cl->debug(stream); return stream; } QDebug& operator<<(QDebug& stream, const ToplevelList& list) { stream << "LIST:("; bool first = true; for (ToplevelList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!first) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } QRect Toplevel::decorationRect() const { return rect(); } void Toplevel::detectShape(Window id) { const bool wasShape = is_shape; is_shape = Xcb::Extensions::self()->hasShape(id); if (wasShape != is_shape) { emit shapedChanged(); } } // used only by Deleted::copy() void Toplevel::copyToDeleted(Toplevel* c) { geom = c->geom; m_visual = c->m_visual; bit_depth = c->bit_depth; info = c->info; m_client.reset(c->m_client, false); ready_for_painting = c->ready_for_painting; damage_handle = None; damage_region = c->damage_region; repaints_region = c->repaints_region; is_shape = c->is_shape; effect_window = c->effect_window; if (effect_window != NULL) effect_window->setWindow(this); resource_name = c->resourceName(); resource_class = c->resourceClass(); m_clientMachine = c->m_clientMachine; m_clientMachine->setParent(this); wmClientLeaderWin = c->wmClientLeader(); opaque_region = c->opaqueRegion(); m_screen = c->m_screen; m_skipCloseAnimation = c->m_skipCloseAnimation; m_internalFBO = c->m_internalFBO; } // before being deleted, remove references to everything that's now // owner by Deleted void Toplevel::disownDataPassedToDeleted() { info = NULL; } QRect Toplevel::visibleRect() const { QRect r = decorationRect(); if (hasShadow() && !shadow()->shadowRegion().isEmpty()) { r |= shadow()->shadowRegion().boundingRect(); } return r.translated(geometry().topLeft()); } Xcb::Property Toplevel::fetchWmClientLeader() const { return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); } void Toplevel::readWmClientLeader(Xcb::Property &prop) { wmClientLeaderWin = prop.value(window()); } void Toplevel::getWmClientLeader() { auto prop = fetchWmClientLeader(); readWmClientLeader(prop); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::sessionId() const { QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, atoms->sm_client_id); return result; } /*! Returns command property for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmCommand() { QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, XCB_ATOM_WM_COMMAND); result.replace(0, ' '); return result; } void Toplevel::getWmClientMachine() { m_clientMachine->resolve(window(), wmClientLeader()); } /*! Returns client machine for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmClientMachine(bool use_localhost) const { if (!m_clientMachine) { // this should never happen return QByteArray(); } if (use_localhost && m_clientMachine->isLocal()) { // special name for the local machine (localhost) return ClientMachine::localhost(); } return m_clientMachine->hostName(); } /*! Returns client leader window for this client. Returns the client window itself if no leader window is defined. */ Window Toplevel::wmClientLeader() const { if (wmClientLeaderWin) return wmClientLeaderWin; return window(); } void Toplevel::getResourceClass() { setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower()); } void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className) { resource_name = name; resource_class = className; emit windowClassChanged(); } double Toplevel::opacity() const { if (info->opacity() == 0xffffffff) return 1.0; return info->opacity() * 1.0 / 0xffffffff; } void Toplevel::setOpacity(double new_opacity) { double old_opacity = opacity(); new_opacity = qBound(0.0, new_opacity, 1.0); if (old_opacity == new_opacity) return; info->setOpacity(static_cast< unsigned long >(new_opacity * 0xffffffff)); if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } void Toplevel::setReadyForPainting() { if (!ready_for_painting) { ready_for_painting = true; if (compositing()) { addRepaintFull(); emit windowShown(this); if (Client *cl = dynamic_cast(this)) { if (cl->tabGroup() && cl->tabGroup()->current() == cl) cl->tabGroup()->setCurrent(cl, true); } } } } void Toplevel::deleteEffectWindow() { delete effect_window; effect_window = NULL; } void Toplevel::checkScreen() { if (screens()->count() == 1) { if (m_screen != 0) { m_screen = 0; emit screenChanged(); } - return; + } else { + const int s = screens()->number(geometry().center()); + if (s != m_screen) { + m_screen = s; + emit screenChanged(); + } } - const int s = screens()->number(geometry().center()); - if (s != m_screen) { - m_screen = s; - emit screenChanged(); + qreal newScale = screens()->scale(m_screen); + if (newScale != m_screenScale) { + m_screenScale = newScale; + emit screenScaleChanged(); } } void Toplevel::setupCheckScreenConnection() { connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(checkScreen())); connect(this, SIGNAL(geometryChanged()), SLOT(checkScreen())); checkScreen(); } void Toplevel::removeCheckScreenConnection() { disconnect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), this, SLOT(checkScreen())); disconnect(this, SIGNAL(geometryChanged()), this, SLOT(checkScreen())); } int Toplevel::screen() const { return m_screen; } +qreal Toplevel::screenScale() const +{ + return m_screenScale; +} + bool Toplevel::isOnScreen(int screen) const { return screens()->geometry(screen).intersects(geometry()); } bool Toplevel::isOnActiveScreen() const { return isOnScreen(screens()->current()); } void Toplevel::getShadow() { QRect dirtyRect; // old & new shadow region const QRect oldVisibleRect = visibleRect(); if (hasShadow()) { dirtyRect = shadow()->shadowRegion().boundingRect(); effectWindow()->sceneWindow()->shadow()->updateShadow(); } else { Shadow::createShadow(this); } if (hasShadow()) dirtyRect |= shadow()->shadowRegion().boundingRect(); if (oldVisibleRect != visibleRect()) emit paddingChanged(this, oldVisibleRect); if (dirtyRect.isValid()) { dirtyRect.translate(pos()); addLayerRepaint(dirtyRect); } } bool Toplevel::hasShadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow() != NULL; } return false; } Shadow *Toplevel::shadow() { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } const Shadow *Toplevel::shadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } bool Toplevel::wantsShadowToBeRendered() const { return true; } void Toplevel::getWmOpaqueRegion() { const auto rects = info->opaqueRegion(); QRegion new_opaque_region; for (const auto &r : rects) { new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } opaque_region = new_opaque_region; } bool Toplevel::isClient() const { return false; } bool Toplevel::isDeleted() const { return false; } bool Toplevel::isOnCurrentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return true; } return isOnActivity(Activities::self()->current()); #else return true; #endif } void Toplevel::elevate(bool elevate) { if (!effectWindow()) { return; } effectWindow()->elevate(elevate); addWorkspaceRepaint(visibleRect()); } pid_t Toplevel::pid() const { return info->pid(); } xcb_window_t Toplevel::frameId() const { return m_client; } Xcb::Property Toplevel::fetchSkipCloseAnimation() const { return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); } void Toplevel::readSkipCloseAnimation(Xcb::Property &property) { setSkipCloseAnimation(property.toBool()); } void Toplevel::getSkipCloseAnimation() { Xcb::Property property = fetchSkipCloseAnimation(); readSkipCloseAnimation(property); } bool Toplevel::skipsCloseAnimation() const { return m_skipCloseAnimation; } void Toplevel::setSkipCloseAnimation(bool set) { if (set == m_skipCloseAnimation) { return; } m_skipCloseAnimation = set; emit skipCloseAnimationChanged(); } void Toplevel::setSurface(KWayland::Server::SurfaceInterface *surface) { if (m_surface == surface) { return; } using namespace KWayland::Server; if (m_surface) { disconnect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); disconnect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); } m_surface = surface; connect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); connect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); connect(m_surface, &SurfaceInterface::subSurfaceTreeChanged, this, [this] { // TODO improve to only update actual visual area if (ready_for_painting) { addDamageFull(); m_isDamaged = true; } } ); connect(m_surface, &SurfaceInterface::destroyed, this, [this] { m_surface = nullptr; } ); emit surfaceChanged(); } void Toplevel::addDamage(const QRegion &damage) { m_isDamaged = true; damage_region += damage; for (const QRect &r : damage.rects()) { emit damaged(this, r); } } QByteArray Toplevel::windowRole() const { return QByteArray(info->windowRole()); } void Toplevel::setDepth(int depth) { if (bit_depth == depth) { return; } const bool oldAlpha = hasAlpha(); bit_depth = depth; if (oldAlpha != hasAlpha()) { emit hasAlphaChanged(); } } QRegion Toplevel::inputShape() const { if (m_surface) { return m_surface->input(); } else { // TODO: maybe also for X11? return QRegion(); } } void Toplevel::setInternalFramebufferObject(const QSharedPointer &fbo) { if (m_internalFBO != fbo) { discardWindowPixmap(); m_internalFBO = fbo; } setDepth(32); } QMatrix4x4 Toplevel::inputTransformation() const { QMatrix4x4 m; m.translate(-x(), -y()); return m; } quint32 Toplevel::windowId() const { return window(); } QRect Toplevel::inputGeometry() const { return geometry(); } } // namespace diff --git a/toplevel.h b/toplevel.h index 18775470a..77ad278bd 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,841 +1,855 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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_TOPLEVEL_H #define KWIN_TOPLEVEL_H // kwin #include "input.h" #include "utils.h" #include "virtualdesktops.h" #include "xcbutils.h" // KDE #include // Qt #include #include // xcb #include #include // XLib #include #include // system #include // c++ #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class ClientMachine; class EffectWindowImpl; class Shadow; /** * Enum to describe the reason why a Toplevel has to be released. */ enum class ReleaseReason { Release, ///< Normal Release after e.g. an Unmap notify event (window still valid) Destroyed, ///< Release after an Destroy notify event (window no longer valid) KWinShutsDown ///< Release on KWin Shutdown (window still valid) }; class KWIN_EXPORT Toplevel : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha NOTIFY hasAlphaChanged) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect visibleRect READ visibleRect) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen NOTIFY screenChanged) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) /** * Whether the window is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName NOTIFY windowClassChanged) Q_PROPERTY(QByteArray resourceClass READ resourceClass NOTIFY windowClassChanged) Q_PROPERTY(QByteArray windowRole READ windowRole NOTIFY windowRoleChanged) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is an On Screen Display. */ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(int windowType READ windowType) Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged) /** * Whether this Toplevel is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isClient CONSTANT) /** * Whether this Toplevel represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted CONSTANT) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ shape NOTIFY shapedChanged) /** * Whether the window does not want to be animated on window close. * There are legit reasons for this like a screenshot application which does not want it's * window being captured. **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation WRITE setSkipCloseAnimation NOTIFY skipCloseAnimationChanged) /** * The Id of the Wayland Surface associated with this Toplevel. * On X11 only setups the value is @c 0. **/ Q_PROPERTY(quint32 surfaceId READ surfaceId NOTIFY surfaceIdChanged) /** * Interface to the Wayland Surface. * Relevant only in Wayland, in X11 it will be nullptr */ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) public: explicit Toplevel(); virtual xcb_window_t frameId() const; xcb_window_t window() const; /** * @return a unique identifier for the Toplevel. On X11 same as @link {window} **/ virtual quint32 windowId() const; QRect geometry() const; /** * The geometry of the Toplevel which accepts input events. This might be larger * than the actual geometry, e.g. to support resizing outside the window. * * Default implementation returns same as geometry. **/ virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; bool isOnScreen(int screen) const; // true if it's at least partially there bool isOnActiveScreen() const; int screen() const; // the screen where the center is + /** + * The scale of the screen this window is currently on + * @Note: The buffer scale can be different. + * @since 5.12 + */ + qreal screenScale() const; // virtual QPoint clientPos() const = 0; // inside of geometry() /** * Describes how the client's content maps to the window geometry including the frame. * The default implementation is a 1:1 mapping meaning the frame is part of the content. **/ virtual QPoint clientContentPos() const; virtual QSize clientSize() const = 0; virtual QRect visibleRect() const; // the area the window occupies on the screen virtual QRect decorationRect() const; // rect including the decoration shadows virtual QRect transparentRect() const = 0; virtual bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types virtual NET::WindowType windowType(bool direct = false, int supported_types = 0) const = 0; bool hasNETSupport() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isDropdownMenu() const; bool isPopupMenu() const; // a context popup, not dropdown, not torn-off bool isTooltip() const; bool isNotification() const; bool isOnScreenDisplay() const; bool isComboBox() const; bool isDNDIcon() const; virtual bool isLockScreen() const; virtual bool isInputMethod() const; /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. */ virtual int desktop() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; bool isOnCurrentDesktop() const; bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; virtual QByteArray windowRole() const; QByteArray sessionId() const; QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; const ClientMachine *clientMachine() const; Window wmClientLeader() const; virtual pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); bool readyForPainting() const; // true if the window has been already painted its contents xcb_visualid_t visual() const; bool shape() const; QRegion inputShape() const; virtual void setOpacity(double opacity); virtual double opacity() const; int depth() const; bool hasAlpha() const; virtual bool setupCompositing(); virtual void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release); Q_INVOKABLE void addRepaint(const QRect& r); Q_INVOKABLE void addRepaint(const QRegion& r); Q_INVOKABLE void addRepaint(int x, int y, int w, int h); Q_INVOKABLE void addLayerRepaint(const QRect& r); Q_INVOKABLE void addLayerRepaint(const QRegion& r); Q_INVOKABLE void addLayerRepaint(int x, int y, int w, int h); Q_INVOKABLE virtual void addRepaintFull(); // these call workspace->addRepaint(), but first transform the damage if needed void addWorkspaceRepaint(const QRect& r); void addWorkspaceRepaint(int x, int y, int w, int h); QRegion repaints() const; void resetRepaints(); QRegion damage() const; void resetDamage(); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * Window will be temporarily painted as if being at the top of the stack. * Only available if Compositor is active, if not active, this method is a no-op. **/ void elevate(bool elevate); /** * @returns Whether the Toplevel has a Shadow or not * @see shadow **/ bool hasShadow() const; /** * Returns the pointer to the Toplevel's Shadow. A Shadow * is only available if Compositing is enabled and the corresponding X window * has the Shadow property set. * If a shadow is available @link hasShadow returns @c true. * @returns The Shadow belonging to this Toplevel, may be @c NULL. * @see hasShadow **/ const Shadow *shadow() const; Shadow *shadow(); /** * Updates the Shadow associated with this Toplevel from X11 Property. * Call this method when the Property changes or Compositing is started. **/ void getShadow(); /** * Whether the Toplevel currently wants the shadow to be rendered. Default * implementation always returns @c true. **/ virtual bool wantsShadowToBeRendered() const; /** * This method returns the area that the Toplevel window reports to be opaque. * It is supposed to only provide valuable information if @link hasAlpha is @c true . * @see hasAlpha **/ const QRegion& opaqueRegion() const; virtual Layer layer() const = 0; /** * Resets the damage state and sends a request for the damage region. * A call to this function must be followed by a call to getDamageRegionReply(), * or the reply will be leaked. * * Returns true if the window was damaged, and false otherwise. */ bool resetAndFetchDamage(); /** * Gets the reply from a previous call to resetAndFetchDamage(). * Calling this function is a no-op if there is no pending reply. * Call damage() to return the fetched region. */ void getDamageRegionReply(); bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); quint32 surfaceId() const; KWayland::Server::SurfaceInterface *surface() const; void setSurface(KWayland::Server::SurfaceInterface *surface); virtual void setInternalFramebufferObject(const QSharedPointer &fbo); const QSharedPointer &internalFramebufferObject() const; /** * @returns Transformation to map from global to window coordinates. * * Default implementation returns a translation on negative pos(). * @see pos **/ virtual QMatrix4x4 inputTransformation() const; /** * The window has a popup grab. This means that when it got mapped the * parent window had an implicit (pointer) grab. * * Normally this is only relevant for transient windows. * * Once the popup grab ends (e.g. pointer press outside of any Toplevel of * the client), the method popupDone should be invoked. * * The default implementation returns @c false. * @see popupDone * @since 5.10 **/ virtual bool hasPopupGrab() const { return false; } /** * This method should be invoked for Toplevels with a popup grab when * the grab ends. * * The default implementation does nothing. * @see hasPopupGrab * @since 5.10 **/ virtual void popupDone() {}; /** * @brief Finds the Toplevel matching the condition expressed in @p func in @p list. * * The method is templated to operate on either a list of Toplevels or on a list of * a subclass type of Toplevel. * @param list The list to search in * @param func The condition function (compare std::find_if) * @return T* The found Toplevel or @c null if there is no matching Toplevel */ template static T *findInList(const QList &list, std::function func); Q_SIGNALS: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); void geometryChanged(); void geometryShapeChanged(KWin::Toplevel* toplevel, const QRect& old); void paddingChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); void windowHidden(KWin::Toplevel* toplevel); /** * Signal emitted when the window's shape state changed. That is if it did not have a shape * and received one or if the shape was withdrawn. Think of Chromium enabling/disabling KWin's * decoration. **/ void shapedChanged(); /** * Emitted whenever the state changes in a way, that the Compositor should * schedule a repaint of the scene. **/ void needsRepaint(); void activitiesChanged(KWin::Toplevel* toplevel); /** * Emitted whenever the Toplevel's screen changes. This can happen either in consequence to * a screen being removed/added or if the Toplevel's geometry changes. * @since 4.11 **/ void screenChanged(); void skipCloseAnimationChanged(); /** * Emitted whenever the window role of the window changes. * @since 5.0 **/ void windowRoleChanged(); /** * Emitted whenever the window class name or resource name of the window changes. * @since 5.0 **/ void windowClassChanged(); /** * Emitted when a Wayland Surface gets associated with this Toplevel. * @since 5.3 **/ void surfaceIdChanged(quint32); /** * @since 5.4 **/ void hasAlphaChanged(); /** * Emitted whenever the Surface for this Toplevel changes. **/ void surfaceChanged(); + /* + * Emitted when the client's screen changes onto a screen of a different scale + * or the screen we're on changes + * @since 5.12 + */ + void screenScaleChanged(); + protected Q_SLOTS: /** * Checks whether the screen number for this Toplevel changed and updates if needed. * Any method changing the geometry of the Toplevel should call this method. **/ void checkScreen(); void setupCheckScreenConnection(); void removeCheckScreenConnection(); void setReadyForPainting(); protected: virtual ~Toplevel(); void setWindowHandles(xcb_window_t client); void detectShape(Window id); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e); virtual void damageNotifyEvent(); virtual void clientMessageEvent(xcb_client_message_event_t *e); void discardWindowPixmap(); void addDamageFull(); virtual void addDamage(const QRegion &damage); Xcb::Property fetchWmClientLeader() const; void readWmClientLeader(Xcb::Property &p); void getWmClientLeader(); void getWmClientMachine(); /** * @returns Whether there is a compositor and it is active. **/ bool compositing() const; /** * This function fetches the opaque region from this Toplevel. * Will only be called on corresponding property changes and for initialization. **/ void getWmOpaqueRegion(); void getResourceClass(); void setResourceClass(const QByteArray &name, const QByteArray &className = QByteArray()); Xcb::Property fetchSkipCloseAnimation() const; void readSkipCloseAnimation(Xcb::Property &prop); void getSkipCloseAnimation(); virtual void debug(QDebug& stream) const = 0; void copyToDeleted(Toplevel* c); void disownDataPassedToDeleted(); friend QDebug& operator<<(QDebug& stream, const Toplevel*); void deleteEffectWindow(); void setDepth(int depth); QRect geom; xcb_visualid_t m_visual; int bit_depth; NETWinInfo* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; protected: bool m_isDamaged; private: // when adding new data members, check also copyToDeleted() Xcb::Window m_client; xcb_damage_damage_t damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; ClientMachine *m_clientMachine; WId wmClientLeaderWin; bool m_damageReplyPending; QRegion opaque_region; xcb_xfixes_fetch_region_cookie_t m_regionCookie; int m_screen; bool m_skipCloseAnimation; quint32 m_surfaceId = 0; KWayland::Server::SurfaceInterface *m_surface = nullptr; /** * An FBO object KWin internal windows might render to. **/ QSharedPointer m_internalFBO; // when adding new data members, check also copyToDeleted() + qreal m_screenScale = 1.0; }; inline xcb_window_t Toplevel::window() const { return m_client; } inline void Toplevel::setWindowHandles(xcb_window_t w) { assert(!m_client.isValid() && w != XCB_WINDOW_NONE); m_client.reset(w, false); } inline QRect Toplevel::geometry() const { return geom; } inline QSize Toplevel::size() const { return geom.size(); } inline QPoint Toplevel::pos() const { return geom.topLeft(); } inline int Toplevel::x() const { return geom.x(); } inline int Toplevel::y() const { return geom.y(); } inline int Toplevel::width() const { return geom.width(); } inline int Toplevel::height() const { return geom.height(); } inline QRect Toplevel::rect() const { return QRect(0, 0, width(), height()); } inline bool Toplevel::readyForPainting() const { return ready_for_painting; } inline xcb_visualid_t Toplevel::visual() const { return m_visual; } inline bool Toplevel::isDesktop() const { return windowType() == NET::Desktop; } inline bool Toplevel::isDock() const { return windowType() == NET::Dock; } inline bool Toplevel::isMenu() const { return windowType() == NET::Menu; } inline bool Toplevel::isToolbar() const { return windowType() == NET::Toolbar; } inline bool Toplevel::isSplash() const { return windowType() == NET::Splash; } inline bool Toplevel::isUtility() const { return windowType() == NET::Utility; } inline bool Toplevel::isDialog() const { return windowType() == NET::Dialog; } inline bool Toplevel::isNormalWindow() const { return windowType() == NET::Normal; } inline bool Toplevel::isDropdownMenu() const { return windowType() == NET::DropdownMenu; } inline bool Toplevel::isPopupMenu() const { return windowType() == NET::PopupMenu; } inline bool Toplevel::isTooltip() const { return windowType() == NET::Tooltip; } inline bool Toplevel::isNotification() const { return windowType() == NET::Notification; } inline bool Toplevel::isOnScreenDisplay() const { return windowType() == NET::OnScreenDisplay; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline bool Toplevel::isLockScreen() const { return false; } inline bool Toplevel::isInputMethod() const { return false; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { return repaints_region.translated(pos()) | layer_repaints_region; } inline bool Toplevel::shape() const { return is_shape; } inline int Toplevel::depth() const { return bit_depth; } inline bool Toplevel::hasAlpha() const { return depth() == 32; } inline const QRegion& Toplevel::opaqueRegion() const { return opaque_region; } inline EffectWindowImpl* Toplevel::effectWindow() { return effect_window; } inline const EffectWindowImpl* Toplevel::effectWindow() const { return effect_window; } inline bool Toplevel::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const { return activities().isEmpty(); } inline bool Toplevel::isOnDesktop(int d) const { return desktop() == d || /*desk == 0 ||*/ isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const { return activities().isEmpty() || activities().contains(activity); } inline bool Toplevel::isOnCurrentDesktop() const { return isOnDesktop(VirtualDesktopManager::self()->current()); } inline QByteArray Toplevel::resourceName() const { return resource_name; // it is always lowercase } inline QByteArray Toplevel::resourceClass() const { return resource_class; // it is always lowercase } inline const ClientMachine *Toplevel::clientMachine() const { return m_clientMachine; } inline quint32 Toplevel::surfaceId() const { return m_surfaceId; } inline KWayland::Server::SurfaceInterface *Toplevel::surface() const { return m_surface; } inline const QSharedPointer &Toplevel::internalFramebufferObject() const { return m_internalFBO; } inline QPoint Toplevel::clientContentPos() const { return QPoint(0, 0); } template inline T *Toplevel::findInList(const QList &list, std::function func) { static_assert(std::is_base_of::value, "U must be derived from T"); const auto it = std::find_if(list.begin(), list.end(), func); if (it == list.end()) { return nullptr; } return *it; } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); } // namespace Q_DECLARE_METATYPE(KWin::Toplevel*) #endif