diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 08811b053..fadd238ab 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,793 +1,798 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 "drm_backend.h" #include "drm_output.h" #include "drm_object_connector.h" #include "drm_object_crtc.h" #include "drm_object_plane.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "main.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include #endif #if HAVE_EGL_STREAMS #include "egl_stream_backend.h" #endif // KWayland #include // KF5 #include #include #include #include // Qt #include #include #include // system #include #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif #define KWIN_DRM_EVENT_CONTEXT_VERSION 2 namespace KWin { DrmBackend::DrmBackend(QObject *parent) : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { #if HAVE_EGL_STREAMS if (qEnvironmentVariableIsSet("KWIN_DRM_USE_EGL_STREAMS")) { m_useEglStreams = true; } #endif setSupportsGammaControl(true); supportsOutputChanges(); } DrmBackend::~DrmBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } } void DrmBackend::prepareShutdown() { writeOutputsConfiguration(); for (DrmOutput *output : m_outputs) { output->teardown(); } Platform::prepareShutdown(); } Outputs DrmBackend::outputs() const { return m_outputs; } Outputs DrmBackend::enabledOutputs() const { return m_enabledOutputs; } void DrmBackend::createDpmsFilter() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prependInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { (*it)->updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { qCDebug(KWIN_DRM) << "Activating session."; reactivate(); } else { qCDebug(KWIN_DRM) << "Deactivating session."; deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; if (!usesSoftwareCursor()) { const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; // only relevant in atomic mode o->m_modesetRequested = true; o->m_crtc->blank(); o->showCursor(); o->moveCursor(cp); } } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } m_devNode = qEnvironmentVariableIsSet("KWIN_DRM_DEVICE_NODE") ? qgetenv("KWIN_DRM_DEVICE_NODE") : QByteArray(device->devNode()); int fd = LogindIntegration::self()->takeDevice(m_devNode.constData()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << m_devNode; return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = KWIN_DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); // trying to activate Atomic Mode Setting (this means also Universal Planes) if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; m_atomicModeSetting = true; DrmScopedPointer planeResources(drmModeGetPlaneResources(m_fd)); if (!planeResources) { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode"; m_atomicModeSetting = false; } if (m_atomicModeSetting) { qCDebug(KWIN_DRM) << "Number of planes:" << planeResources->count_planes; // create the plane objects for (unsigned int i = 0; i < planeResources->count_planes; ++i) { drmModePlane *kplane = drmModeGetPlane(m_fd, planeResources->planes[i]); DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); if (p->atomicInit()) { m_planes << p; if (p->type() == DrmPlane::TypeIndex::Overlay) { m_overlayPlanes << p; } } else { delete p; } } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode"; m_atomicModeSetting = false; } } } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode."; } } DrmScopedPointer resources(drmModeGetResources(m_fd)); drmModeRes *res = resources.data(); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } for (int i = 0; i < res->count_connectors; ++i) { m_connectors << new DrmConnector(res->connectors[i], m_fd); } for (int i = 0; i < res->count_crtcs; ++i) { m_crtcs << new DrmCrtc(res->crtcs[i], this, i); } if (m_atomicModeSetting) { auto tryAtomicInit = [] (DrmObject *obj) -> bool { if (obj->atomicInit()) { return false; } else { delete obj; return true; } }; m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryAtomicInit), m_connectors.end()); m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryAtomicInit), m_crtcs.end()); } initCursor(); updateOutputs(); if (m_outputs.isEmpty()) { qCDebug(KWIN_DRM) << "No connected outputs found on startup."; } // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; updateOutputs(); updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); } void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } DrmScopedPointer resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); m_enabledOutputs.removeOne(removed); emit outputRemoved(removed); removed->teardown(); } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { DrmScopedPointer connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { DrmScopedPointer encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning DrmScopedPointer modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this); con->setOutput(output); output->m_conn = con; crtc->setOutput(output); output->m_crtc = crtc; if (modeCrtc->mode_valid) { output->m_mode = modeCrtc->mode; } else { output->m_mode = connector->modes[0]; } qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); delete output; continue; } if (!output->initCursor(m_cursorSize)) { setSoftWareCursor(true); } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; m_enabledOutputs = connectedOutputs; readOutputsConfiguration(); updateOutputsEnabled(); if (!m_outputs.isEmpty()) { emit screensQueried(); } } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode if (outputConfig.hasKey("Scale")) (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } void DrmBackend::writeOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); auto configGroup = KSharedConfig::openConfig()->group("DrmOutputs").group(uuid); // default position goes from left to right for (auto it = m_outputs.cbegin(); it != m_outputs.cend(); ++it) { qCDebug(KWIN_DRM) << "Writing output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; auto outputConfig = configGroup.group((*it)->uuid()); outputConfig.writeEntry("Scale", (*it)->scale()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::enableOutput(DrmOutput *output, bool enable) { if (enable) { Q_ASSERT(!m_enabledOutputs.contains(output)); m_enabledOutputs << output; emit outputAdded(output); } else { Q_ASSERT(m_enabledOutputs.contains(output)); m_enabledOutputs.removeOne(output); Q_ASSERT(!m_enabledOutputs.contains(output)); emit outputRemoved(output); } updateOutputsEnabled(); checkOutputsAreOn(); emit screensQueried(); } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } bool DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { if (m_deleteBufferAfterPageFlip) { delete buffer; } return false; } if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } return true; } else if (m_deleteBufferAfterPageFlip) { delete buffer; } return false; } void DrmBackend::initCursor() { #if HAVE_EGL_STREAMS // Hardware cursors aren't currently supported with EGLStream backend, // possibly an NVIDIA driver bug if (m_useEglStreams) { setSoftWareCursor(true); } #endif m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { m_cursorEnabled = waylandServer()->seat()->hasPointer(); if (usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (m_cursorEnabled) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } else { (*it)->hideCursor(); } } } ); uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } m_cursorSize = cursorSize; // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->updateCursor(); } setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled || usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { if (!m_cursorEnabled || isCursorHidden() || usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(Cursor::pos()); } } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_EGL_STREAMS if (m_useEglStreams) { m_deleteBufferAfterPageFlip = false; return new EglStreamBackend(this); } #endif #if HAVE_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { DrmDumbBuffer *b = new DrmDumbBuffer(m_fd, size); return b; } #if HAVE_GBM DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) { DrmSurfaceBuffer *b = new DrmSurfaceBuffer(m_fd, surface); return b; } #endif void DrmBackend::updateOutputsEnabled() { bool enabled = false; for (auto it = m_enabledOutputs.constBegin(); it != m_enabledOutputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } QVector DrmBackend::supportedCompositors() const { if (selectedCompositor() != NoCompositing) { return {selectedCompositor()}; } #if HAVE_GBM return QVector{OpenGLCompositing, QPainterCompositing}; #elif HAVE_EGL_STREAMS return m_useEglStreams ? QVector{OpenGLCompositing, QPainterCompositing} : QVector{QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } QString DrmBackend::supportInformation() const { QString supportInfo; QDebug s(&supportInfo); s.nospace(); - s << "Name: " << "DRM" << Qt::endl; - s << "Active: " << m_active << Qt::endl; - s << "Atomic Mode Setting: " << m_atomicModeSetting << Qt::endl; + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + using Qt::endl; +#endif + + s << "Name: " << "DRM" << endl; + s << "Active: " << m_active << endl; + s << "Atomic Mode Setting: " << m_atomicModeSetting << endl; #if HAVE_EGL_STREAMS - s << "Using EGL Streams: " << m_useEglStreams << Qt::endl; + s << "Using EGL Streams: " << m_useEglStreams << endl; #endif return supportInfo; } } diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index 1ea6d4e6b..6d9a4879d 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,972 +1,975 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. 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 . *********************************************************************/ // own #include "glxbackend.h" #include "logging.h" #include "glx_context_attribute_builder.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "xcbutils.h" #include "texture.h" // kwin libs #include #include #include #include // Qt #include #include #include #include // system #include #include #include #if HAVE_DL_LIBRARY #include #endif #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #include namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , window(None) , fbconfig(nullptr) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , haveSwapInterval(false) , m_x11Display(display) { // Ensures calls to glXSwapBuffers will always block until the next // retrace when using the proprietary NVIDIA driver. This must be // set before libGL.so is loaded. setenv("__GL_MaxFramesAllowed", "1", true); // Force initialization of GLX integration in the Qt's xcb backend // to make it call XESetWireToEvent callbacks, which is required // by Mesa when using DRI2. QOpenGLContext::supportsThreadedOpenGL(); } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); EffectQuickView::setShareContext(nullptr); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } typedef void (*glXFuncPtr)(); static glXFuncPtr getProcAddress(const char* name) { glXFuncPtr ret = nullptr; #if HAVE_EPOXY_GLX ret = glXGetProcAddress((const GLubyte*) name); #endif #if HAVE_DL_LIBRARY if (ret == nullptr) ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); #endif return ret; } glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { // Require at least GLX 1.3 if (!checkVersion()) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initExtensions(); // resolve glXSwapIntervalMESA if available if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); } else { glXSwapIntervalMESA = nullptr; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); // Check whether certain features are supported m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } setSyncsToVBlank(false); setBlocksForRetrace(false); haveWaitSync = false; gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable setSwapInterval(1); setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { unsigned int sync; if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { setSyncsToVBlank(true); setBlocksForRetrace(true); haveWaitSync = true; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. glXQueryDrawable = nullptr; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::checkVersion() { int major, minor; glXQueryVersion(display(), &major, &minor); return kVersionNumber(major, minor) >= kVersionNumber(1, 3); } void GlxBackend::initExtensions() { const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); setExtensions(string.split(' ')); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); const bool haveVideoMemoryPurge = hasExtension(QByteArrayLiteral("GLX_NV_robustness_video_memory_purge")); std::vector candidates; if (options->glCoreProfile()) { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryCore; purgeMemoryCore.setVersion(3, 1); purgeMemoryCore.setRobust(true); purgeMemoryCore.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryCore)); } GlxContextAttributeBuilder robustCore; robustCore.setVersion(3, 1); robustCore.setRobust(true); candidates.emplace_back(std::move(robustCore)); } GlxContextAttributeBuilder core; core.setVersion(3, 1); candidates.emplace_back(std::move(core)); } else { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryLegacy; purgeMemoryLegacy.setRobust(true); purgeMemoryLegacy.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryLegacy)); } GlxContextAttributeBuilder robustLegacy; robustLegacy.setRobust(true); candidates.emplace_back(std::move(robustLegacy)); } GlxContextAttributeBuilder legacy; legacy.setVersion(2, 1); candidates.emplace_back(std::move(legacy)); } for (auto it = candidates.begin(); it != candidates.end(); it++) { const auto attribs = it->build(); ctx = glXCreateContextAttribsARB(display(), fbconfig, nullptr, true, attribs.data()); if (ctx) { qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it); break; } } } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, nullptr, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); ctx = nullptr; return false; } auto qtContext = new QOpenGLContext; QGLXNativeContext native(ctx, display()); qtContext->setNativeHandle(QVariant::fromValue(native)); qtContext->create(); EffectQuickView::setShareContext(std::unique_ptr(qtContext)); return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); glxWindow = glXCreateWindow(display(), fbconfig, window, nullptr); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; const int attribs_srgb[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, true, 0 }; bool llvmpipe = false; // Note that we cannot use GLPlatform::driver() here, because it has not been initialized at this point if (hasExtension(QByteArrayLiteral("GLX_MESA_query_renderer"))) { const QByteArray device = glXQueryRendererStringMESA(display(), DefaultScreen(display()), 0, GLX_RENDERER_DEVICE_ID_MESA); if (device.contains(QByteArrayLiteral("llvmpipe"))) { llvmpipe = true; } } // Try to find a double buffered sRGB capable configuration int count = 0; GLXFBConfig *configs = nullptr; // Don't request an sRGB configuration with LLVMpipe when the default depth is 16. See bug #408594. if (!llvmpipe || Xcb::defaultDepth() > 16) { configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs_srgb, &count); } if (count == 0) { // Try to find a double buffered non-sRGB capable configuration configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); } struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil, srgb; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil, srgb); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } static inline int bitCount(uint32_t mask) { #if defined(__GNUC__) return __builtin_popcount(mask); #else int count = 0; while (mask) { count += (mask & 1); mask >>= 1; } return count; #endif } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + using Qt::hex; +#endif if (!direct) { - qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << Qt::hex << visual; + qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE), // The ARGB32 visual is marked sRGB capable in mesa/i965 GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { - qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << Qt::hex << visual; + qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); - qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << Qt::hex << fbc_id << " for visual 0x" << Qt::hex << visual_id; + qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::setSwapInterval(int interval) { if (m_haveEXTSwapControl) glXSwapIntervalEXT(display(), glxWindow, interval); else if (m_haveMESASwapControl) glXSwapIntervalMESA(interval); else if (m_haveSGISwapControl) glXSwapIntervalSGI(interval); } void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering if (haveWaitSync) { uint sync; #if 0 // TODO: why precisely is this important? // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync // but this only leads to waiting for two frames??!? glXGetVideoSync(&sync); glXWaitVideoSync(2, (sync + 1) % 2, &sync); #else glXWaitVideoSyncSGI(1, 0, &sync); #endif } } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); if (fullRepaint) { if (m_haveINTELSwapEvent) Compositor::self()->aboutToSwapBuffers(); if (haveSwapInterval) { if (gs_tripleBufferNeedsDetection) { glXWaitGL(); m_swapProfiler.begin(); } glXSwapBuffers(display(), glxWindow); if (gs_tripleBufferNeedsDetection) { glXWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; setBlocksForRetrace(result == 'd'); } } } else { waitSync(); glXSwapBuffers(display(), glxWindow); } if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { for (const QRect &r : lastDamage()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); glXWaitX(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() const { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { Q_ASSERT(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->bufferGeometry().size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace