diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 402d3b48f..a8bcc9eb2 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,406 +1,413 @@ add_definitions(-DKWIN_UNIT_TEST) remove_definitions(-DQT_USE_QSTRINGBUILDER) add_subdirectory(libkwineffects) add_subdirectory(libxrenderutils) add_subdirectory(integration) if (HAVE_INPUT) add_subdirectory(libinput) endif() add_subdirectory(tabbox) ######################################################## # Test ScreenPaintData ######################################################## set( testScreenPaintData_SRCS test_screen_paint_data.cpp ) add_executable(testScreenPaintData ${testScreenPaintData_SRCS}) target_link_libraries( testScreenPaintData kwineffects Qt5::Test Qt5::Widgets KF5::WindowSystem) add_test(kwin-testScreenPaintData testScreenPaintData) ecm_mark_as_test(testScreenPaintData) ######################################################## # Test WindowPaintData ######################################################## set( testWindowPaintData_SRCS test_window_paint_data.cpp ) add_executable(testWindowPaintData ${testWindowPaintData_SRCS}) target_link_libraries( testWindowPaintData kwineffects Qt5::Widgets Qt5::Test ) add_test(kwin-testWindowPaintData testWindowPaintData) ecm_mark_as_test(testWindowPaintData) ######################################################## # Test VirtualDesktopManager ######################################################## set( testVirtualDesktops_SRCS test_virtual_desktops.cpp ../virtualdesktops.cpp ) add_executable(testVirtualDesktops ${testVirtualDesktops_SRCS}) target_link_libraries( testVirtualDesktops Qt5::Test Qt5::Widgets KF5::I18n KF5::GlobalAccel KF5::ConfigCore KF5::WindowSystem ) add_test(kwin-testVirtualDesktops testVirtualDesktops) ecm_mark_as_test(testVirtualDesktops) ######################################################## # Test ClientMachine ######################################################## set( testClientMachine_SRCS test_client_machine.cpp ../client_machine.cpp ) add_executable( testClientMachine ${testClientMachine_SRCS} ) set_target_properties(testClientMachine PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testClientMachine Qt5::Concurrent Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::XFIXES ${X11_X11_LIB} # to make jenkins happy ) add_test(kwin-testClientMachine testClientMachine) ecm_mark_as_test(testClientMachine) ######################################################## # Test XcbWrapper ######################################################## set( testXcbWrapper_SRCS test_xcb_wrapper.cpp ) add_executable( testXcbWrapper ${testXcbWrapper_SRCS} ) target_link_libraries( testXcbWrapper Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) add_test(kwin-testXcbWrapper testXcbWrapper) ecm_mark_as_test(testXcbWrapper) if (XCB_ICCCM_FOUND) add_executable( testXcbSizeHints test_xcb_size_hints.cpp ) set_target_properties(testXcbSizeHints PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries( testXcbSizeHints Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::ICCCM ) add_test(kwin-testXcbSizeHints testXcbSizeHints) ecm_mark_as_test(testXcbSizeHints) endif() ######################################################## # Test XcbWindow ######################################################## set( testXcbWindow_SRCS test_xcb_window.cpp ) add_executable( testXcbWindow ${testXcbWindow_SRCS} ) target_link_libraries( testXcbWindow Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) add_test(kwin-testXcbWindow testXcbWindow) ecm_mark_as_test(testXcbWindow) ######################################################## # Test BuiltInEffectLoader ######################################################## set( testBuiltInEffectLoader_SRCS test_builtin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testBuiltInEffectLoader ${testBuiltInEffectLoader_SRCS}) set_target_properties(testBuiltInEffectLoader PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_link_libraries(testBuiltInEffectLoader Qt5::Concurrent Qt5::Test Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testBuiltInEffectLoader testBuiltInEffectLoader) ecm_mark_as_test(testBuiltInEffectLoader) ######################################################## # Test ScriptedEffectLoader ######################################################## include_directories(${KWIN_SOURCE_DIR}) set( testScriptedEffectLoader_SRCS test_scripted_effectloader.cpp mock_abstract_client.cpp mock_effectshandler.cpp mock_screens.cpp mock_workspace.cpp ../effectloader.cpp ../scripting/scriptedeffect.cpp ../scripting/scriptingutils.cpp ../scripting/scripting_logging.cpp ../screens.cpp ) kconfig_add_kcfg_files(testScriptedEffectLoader_SRCS ../settings.kcfgc) add_executable( testScriptedEffectLoader ${testScriptedEffectLoader_SRCS}) target_link_libraries(testScriptedEffectLoader Qt5::Concurrent Qt5::Qml Qt5::Script Qt5::Test Qt5::X11Extras KF5::ConfigGui KF5::GlobalAccel KF5::I18n KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testScriptedEffectLoader testScriptedEffectLoader) ecm_mark_as_test(testScriptedEffectLoader) ######################################################## # Test PluginEffectLoader ######################################################## set( testPluginEffectLoader_SRCS test_plugin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testPluginEffectLoader ${testPluginEffectLoader_SRCS}) target_link_libraries(testPluginEffectLoader Qt5::Concurrent Qt5::Test Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testPluginEffectLoader testPluginEffectLoader) ecm_mark_as_test(testPluginEffectLoader) ######################################################## # FakeEffectPlugin ######################################################## add_library(fakeeffectplugin MODULE fakeeffectplugin.cpp) set_target_properties(fakeeffectplugin PROPERTIES PREFIX "") target_link_libraries(fakeeffectplugin kwineffects) ######################################################## # FakeEffectPlugin-Version ######################################################## add_library(effectversionplugin MODULE fakeeffectplugin_version.cpp) set_target_properties(effectversionplugin PROPERTIES PREFIX "") target_link_libraries(effectversionplugin kwineffects) ######################################################## # Test Screens ######################################################## set( testScreens_SRCS test_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../x11eventfilter.cpp ) kconfig_add_kcfg_files(testScreens_SRCS ../settings.kcfgc) add_executable( testScreens ${testScreens_SRCS}) target_include_directories(testScreens BEFORE PRIVATE ./) target_link_libraries(testScreens Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::ConfigGui KF5::WindowSystem ) add_test(kwin_testScreens testScreens) ecm_mark_as_test(testScreens) ######################################################## # Test XrandRScreens ######################################################## set( testXRandRScreens_SRCS test_xrandr_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../plugins/platforms/x11/standalone/screens_xrandr.cpp ../xcbutils.cpp # init of extensions ../x11eventfilter.cpp ) kconfig_add_kcfg_files(testXRandRScreens_SRCS ../settings.kcfgc) add_executable( testXRandRScreens ${testXRandRScreens_SRCS} ) target_link_libraries( testXRandRScreens Qt5::Test Qt5::Gui KF5::ConfigCore KF5::ConfigGui KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) add_test(kwin-testXRandRScreens testXRandRScreens) ecm_mark_as_test(testXRandRScreens) ######################################################## # Test ScreenEdges ######################################################## set( testScreenEdges_SRCS test_screen_edges.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../atoms.cpp ../gestures.cpp ../screens.cpp ../screenedge.cpp ../virtualdesktops.cpp ../xcbutils.cpp # init of extensions ../plugins/platforms/x11/standalone/edge.cpp ) kconfig_add_kcfg_files(testScreenEdges_SRCS ../settings.kcfgc) qt5_add_dbus_interface( testScreenEdges_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.freedesktop.ScreenSaver.xml screenlocker_interface) add_executable( testScreenEdges ${testScreenEdges_SRCS}) set_target_properties(testScreenEdges PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") target_include_directories(testScreenEdges BEFORE PRIVATE ./) target_link_libraries(testScreenEdges Qt5::DBus Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::ConfigGui KF5::I18n KF5::GlobalAccel KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) add_test(kwin_testScreenEdges testScreenEdges) ecm_mark_as_test(testScreenEdges) ######################################################## # Test OnScreenNotification ######################################################## set( testOnScreenNotification_SRCS onscreennotificationtest.cpp ../onscreennotification.cpp ../input_event_spy.cpp ) add_executable( testOnScreenNotification ${testOnScreenNotification_SRCS}) target_link_libraries(testOnScreenNotification Qt5::Test Qt5::Widgets # QAction include Qt5::Quick KF5::ConfigCore ) add_test(kwin-testOnScreenNotification testOnScreenNotification) ecm_mark_as_test(testOnScreenNotification) ######################################################## # Test Gestures ######################################################## set( testGestures_SRCS test_gestures.cpp ../gestures.cpp ) add_executable( testGestures ${testGestures_SRCS}) target_link_libraries(testGestures Qt5::Test ) add_test(kwin-testGestures testGestures) ecm_mark_as_test(testGestures) ######################################################## # Test X11 TimestampUpdate ######################################################## add_executable(testX11TimestampUpdate test_x11_timestamp_update.cpp) target_compile_definitions(testX11TimestampUpdate PRIVATE KWINBACKENDPATH="${CMAKE_BINARY_DIR}/plugins/platforms/x11/standalone/KWinX11Platform.so") target_link_libraries(testX11TimestampUpdate Qt5::Test KF5::CoreAddons kwin ) add_test(kwin-testX11TimestampUpdate testX11TimestampUpdate) ecm_mark_as_test(testX11TimestampUpdate) set(testOpenGLContextAttributeBuilder_SRCS opengl_context_attribute_builder_test.cpp ../abstract_opengl_context_attribute_builder.cpp ../egl_context_attribute_builder.cpp ) if(HAVE_EPOXY_GLX) set(testOpenGLContextAttributeBuilder_SRCS ${testOpenGLContextAttributeBuilder_SRCS} ../plugins/platforms/x11/standalone/glx_context_attribute_builder.cpp) endif() add_executable(testOpenGLContextAttributeBuilder ${testOpenGLContextAttributeBuilder_SRCS}) target_link_libraries(testOpenGLContextAttributeBuilder Qt5::Test) add_test(kwin-testOpenGLContextAttributeBuilder testOpenGLContextAttributeBuilder) ecm_mark_as_test(testOpenGLContextAttributeBuilder) set(testXkb_SRCS test_xkb.cpp ../xkb.cpp ) add_executable(testXkb ${testXkb_SRCS}) target_link_libraries(testXkb Qt5::Test Qt5::Gui Qt5::Widgets KF5::ConfigCore KF5::WindowSystem KF5::WaylandServer XKB::XKB ) add_test(kwin-testXkb testXkb) ecm_mark_as_test(testXkb) + +if(HAVE_GBM) + add_executable(testGbmSurface test_gbm_surface.cpp ../plugins/platforms/drm/gbm_surface.cpp) + target_link_libraries(testGbmSurface Qt5::Test) + add_test(kwin-testGbmSurface testGbmSurface) + ecm_mark_as_test(kwin-testGbmSurface) +endif() diff --git a/autotests/test_gbm_surface.cpp b/autotests/test_gbm_surface.cpp new file mode 100644 index 000000000..95ee36386 --- /dev/null +++ b/autotests/test_gbm_surface.cpp @@ -0,0 +1,119 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Flöser + +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 "../plugins/platforms/drm/gbm_surface.h" +#include + +#include + +// mocking + +struct gbm_device { + bool surfaceShouldFail = false; +}; + +struct gbm_surface { + uint32_t width; + uint32_t height; + uint32_t format; + uint32_t flags; +}; + +struct gbm_bo { +}; + +struct gbm_surface *gbm_surface_create(struct gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, uint32_t flags) +{ + if (gbm && gbm->surfaceShouldFail) { + return nullptr; + } + auto ret = new gbm_surface{width, height, format, flags}; + return ret; +} + +void gbm_surface_destroy(struct gbm_surface *surface) +{ + delete surface; +} + +struct gbm_bo *gbm_surface_lock_front_buffer(struct gbm_surface *surface) +{ + Q_UNUSED(surface) + return new gbm_bo; +} + +void gbm_surface_release_buffer(struct gbm_surface *surface, struct gbm_bo *bo) +{ + Q_UNUSED(surface) + delete bo; +} + +using KWin::GbmSurface; + +class GbmSurfaceTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testCreate(); + void testCreateFailure(); + void testBo(); +}; + +void GbmSurfaceTest::testCreate() +{ + GbmSurface surface(nullptr, 2, 3, 4, 5); + gbm_surface *native = surface; + QVERIFY(surface); + QCOMPARE(native->width, 2u); + QCOMPARE(native->height, 3u); + QCOMPARE(native->format, 4u); + QCOMPARE(native->flags, 5u); +} + +void GbmSurfaceTest::testCreateFailure() +{ + gbm_device dev{true}; + GbmSurface surface(&dev, 2, 3, 4, 5); + QVERIFY(!surface); + gbm_surface *native = surface; + QVERIFY(!native); +} + +void GbmSurfaceTest::testBo() +{ + GbmSurface surface(nullptr, 2, 3, 4, 5); + // release buffer on nullptr should not be a problem + surface.releaseBuffer(nullptr); + // now an actual buffer + auto bo = surface.lockFrontBuffer(); + surface.releaseBuffer(bo); + + // and a surface which fails + gbm_device dev{true}; + GbmSurface surface2(&dev, 2, 3, 4, 5); + QVERIFY(!surface2.lockFrontBuffer()); + auto bo2 = surface.lockFrontBuffer(); + // this won't do anything + surface2.releaseBuffer(bo2); + // so we need to clean up properly + surface.releaseBuffer(bo2); +} + +QTEST_GUILESS_MAIN(GbmSurfaceTest) +#include "test_gbm_surface.moc" diff --git a/plugins/platforms/drm/CMakeLists.txt b/plugins/platforms/drm/CMakeLists.txt index 8db5207a7..cc9141c90 100644 --- a/plugins/platforms/drm/CMakeLists.txt +++ b/plugins/platforms/drm/CMakeLists.txt @@ -1,31 +1,31 @@ set(DRM_SOURCES drm_backend.cpp drm_object.cpp drm_object_connector.cpp drm_object_crtc.cpp drm_object_plane.cpp drm_output.cpp drm_buffer.cpp drm_inputeventfilter.cpp logging.cpp scene_qpainter_drm_backend.cpp screens_drm.cpp ) if(HAVE_GBM) - set(DRM_SOURCES ${DRM_SOURCES} egl_gbm_backend.cpp drm_buffer_gbm.cpp) + set(DRM_SOURCES ${DRM_SOURCES} egl_gbm_backend.cpp drm_buffer_gbm.cpp gbm_surface.cpp) endif() add_library(KWinWaylandDrmBackend MODULE ${DRM_SOURCES}) target_link_libraries(KWinWaylandDrmBackend kwin Libdrm::Libdrm SceneQPainterBackend) if(HAVE_GBM) target_link_libraries(KWinWaylandDrmBackend gbm::gbm) endif() install( TARGETS KWinWaylandDrmBackend DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.waylandbackends/ ) diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 33f894d41..9a51aca2b 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,736 +1,736 @@ /******************************************************************** 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 // KWayland #include #include // KF5 #include #include #include // Qt #include #include #include // system #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() { handleOutputs(); m_cursor[0] = nullptr; m_cursor[1] = nullptr; } 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_outputs); qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); delete m_cursor[0]; delete m_cursor[1]; 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::outputWentOff() { 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_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { (*it)->setDpms(DrmOutput::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_outputs.constBegin(), end = m_outputs.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()) { DrmDumbBuffer *c = m_cursor[(m_cursorIndex + 1) % 2]; 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->pageFlipped(); // TODO: Do we really need this? o->m_crtc->blank(); o->showCursor(c); 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 (output->m_dpmsAtomicOffPending) { output->m_modesetRequested = true; output->dpmsAtomicOff(); } 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; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->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; ScopedDrmPointer 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, this); 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."; } } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> 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], this); } 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()); } updateOutputs(); if (m_outputs.isEmpty()) { qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now"; emit initFailed(); return; } // 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(); m_cursorIndex = (m_cursorIndex + 1) % 2; updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); initCursor(); } void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> 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); emit outputRemoved(removed); delete removed; } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> 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)) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> 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 ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> 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; connect(output, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); 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(); con->setOutput(nullptr); crtc->setOutput(nullptr); delete output; continue; } 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; readOutputsConfiguration(); 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 (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } 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::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); return; } drmoutput->setChanges(changeset); } emit screens()->changed(); } 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; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { if (m_deleteBufferAfterPageFlip) { delete buffer; } return; } if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } else if (m_deleteBufferAfterPageFlip) { delete buffer; } } void DrmBackend::initCursor() { 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) { (*it)->showCursor(m_cursor[m_cursorIndex]); } 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); } auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } m_cursor[index]->image()->fill(Qt::transparent); return true; }; if (!createCursor(0) || !createCursor(1)) { setSoftWareCursor(true); return; } // 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() { DrmDumbBuffer *c = m_cursor[m_cursorIndex]; m_cursorIndex = (m_cursorIndex + 1) % 2; if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->showCursor(c); } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.drawImage(QPoint(0, 0), cursorImage); p.end(); setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { if (!m_cursorEnabled || isCursorHidden()) { 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_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { DrmDumbBuffer *b = new DrmDumbBuffer(this, size); return b; } #if HAVE_GBM -DrmSurfaceBuffer *DrmBackend::createBuffer(gbm_surface *surface) +DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) { DrmSurfaceBuffer *b = new DrmSurfaceBuffer(this, surface); return b; } #endif void DrmBackend::outputDpmsChanged() { if (m_outputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } } diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h index 96a8d0ce6..c44d3f018 100644 --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -1,175 +1,178 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H #include "platform.h" #include "input.h" #include "drm_buffer.h" #if HAVE_GBM #include "drm_buffer_gbm.h" #endif #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include +#include + struct gbm_bo; struct gbm_device; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; class DrmOutput; class DrmPlane; class DrmCrtc; class DrmConnector; +class GbmSurface; class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmDumbBuffer *createBuffer(const QSize &size); #if HAVE_GBM - DrmSurfaceBuffer *createBuffer(gbm_surface *surface); + DrmSurfaceBuffer *createBuffer(const std::shared_ptr &surface); #endif void present(DrmBuffer *buffer, DrmOutput *output); int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } QVector planes() const { return m_planes; } QVector overlayPlanes() const { return m_overlayPlanes; } void outputWentOff(); void checkOutputsAreOn(); // QPainter reuses buffers bool deleteBufferAfterPageFlip() const { return m_deleteBufferAfterPageFlip; } // returns use of AMS, default is not/legacy bool atomicModeSetting() const { return m_atomicModeSetting; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } gbm_device *gbmDevice() const { return m_gbmDevice; } public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); protected: void doHideCursor() override; void doShowCursor() override; private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void updateOutputs(); void setCursor(); void updateCursor(); void moveCursor(); void initCursor(); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; // all crtcs QVector m_crtcs; // all connectors QVector m_connectors; // currently active output pipelines (planes + crtc + encoder + connector) QVector m_outputs; DrmDumbBuffer *m_cursor[2]; bool m_deleteBufferAfterPageFlip; bool m_atomicModeSetting = false; bool m_cursorEnabled = false; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; // all available planes: primarys, cursors and overlays QVector m_planes; QVector m_overlayPlanes; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; gbm_device *m_gbmDevice = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_buffer_gbm.cpp b/plugins/platforms/drm/drm_buffer_gbm.cpp index 8cebfc051..6959d1bd9 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.cpp +++ b/plugins/platforms/drm/drm_buffer_gbm.cpp @@ -1,69 +1,68 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg Copyright 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_buffer_gbm.h" +#include "gbm_surface.h" #include "logging.h" // system #include #include // drm #include #include namespace KWin { // DrmSurfaceBuffer -DrmSurfaceBuffer::DrmSurfaceBuffer(DrmBackend *backend, gbm_surface *surface) +DrmSurfaceBuffer::DrmSurfaceBuffer(DrmBackend *backend, const std::shared_ptr &surface) : DrmBuffer(backend) , m_surface(surface) { - m_bo = gbm_surface_lock_front_buffer(surface); + m_bo = m_surface->lockFrontBuffer(); if (!m_bo) { qCWarning(KWIN_DRM) << "Locking front buffer failed"; return; } m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { qCWarning(KWIN_DRM) << "drmModeAddFB failed"; } gbm_bo_set_user_data(m_bo, this, nullptr); } DrmSurfaceBuffer::~DrmSurfaceBuffer() { if (m_bufferId) { drmModeRmFB(m_backend->fd(), m_bufferId); } releaseGbm(); } void DrmSurfaceBuffer::releaseGbm() { - if (m_bo) { - gbm_surface_release_buffer(m_surface, m_bo); - m_bo = nullptr; - } + m_surface->releaseBuffer(m_bo); + m_bo = nullptr; } } diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/plugins/platforms/drm/drm_buffer_gbm.h index 82224b116..d26f66556 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/plugins/platforms/drm/drm_buffer_gbm.h @@ -1,61 +1,63 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg Copyright 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 . *********************************************************************/ #ifndef KWIN_DRM_BUFFER_GBM_H #define KWIN_DRM_BUFFER_GBM_H #include "drm_buffer.h" +#include + struct gbm_bo; -struct gbm_surface; namespace KWin { class DrmBackend; +class GbmSurface; class DrmSurfaceBuffer : public DrmBuffer { public: - DrmSurfaceBuffer(DrmBackend *backend, gbm_surface *surface); + DrmSurfaceBuffer(DrmBackend *backend, const std::shared_ptr &surface); ~DrmSurfaceBuffer(); bool needsModeChange(DrmBuffer *b) const override { if (DrmSurfaceBuffer *sb = dynamic_cast(b)) { return hasBo() != sb->hasBo(); } else { return true; } } bool hasBo() const { return m_bo != nullptr; } void releaseGbm() override; private: - gbm_surface *m_surface = nullptr; + std::shared_ptr m_surface; gbm_bo *m_bo = nullptr; }; } #endif diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp index 646e77566..51792b541 100644 --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -1,353 +1,352 @@ /******************************************************************** 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 "egl_gbm_backend.h" // kwin #include "composite.h" #include "drm_backend.h" #include "drm_output.h" +#include "gbm_surface.h" #include "logging.h" #include "options.h" #include "screens.h" // kwin libs #include // Qt #include // system #include namespace KWin { EglGbmBackend::EglGbmBackend(DrmBackend *b) : AbstractEglBackend() , m_backend(b) { // Egl is always direct rendering setIsDirectRendering(true); setSyncsToVBlank(true); connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); connect(m_backend, &DrmBackend::outputRemoved, this, [this] (DrmOutput *output) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [output] (const Output &o) { return o.output == output; } ); if (it == m_outputs.end()) { return; } cleanupOutput(*it); m_outputs.erase(it); } ); } EglGbmBackend::~EglGbmBackend() { cleanup(); } void EglGbmBackend::cleanupSurfaces() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { cleanupOutput(*it); } + m_outputs.clear(); } void EglGbmBackend::cleanupOutput(const Output &o) { o.output->releaseGbm(); if (o.eglSurface != EGL_NO_SURFACE) { eglDestroySurface(eglDisplay(), o.eglSurface); } - if (o.gbmSurface) { - gbm_surface_destroy(o.gbmSurface); - } } bool EglGbmBackend::initializeEgl() { initClientExtensions(); EGLDisplay display = m_backend->sceneEglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (display == EGL_NO_DISPLAY) { if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) || !hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { setFailed("EGL_EXT_platform_base and/or EGL_MESA_platform_gbm missing"); return false; } auto device = gbm_create_device(m_backend->fd()); if (!device) { setFailed("Could not create gbm device"); return false; } m_backend->setGbmDevice(device); display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, device, nullptr); } if (display == EGL_NO_DISPLAY) return false; setEglDisplay(display); return initEglAPI(); } void EglGbmBackend::init() { if (!initializeEgl()) { setFailed("Could not initialize egl"); return; } if (!initRenderingContext()) { setFailed("Could not initialize rendering context"); return; } initKWinGL(); initBufferAge(); initWayland(); } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); if (!createContext()) { return false; } const auto outputs = m_backend->outputs(); for (DrmOutput *drmOutput: outputs) { createOutput(drmOutput); } if (m_outputs.isEmpty()) { qCCritical(KWIN_DRM) << "Create Window Surfaces failed"; return false; } // set our first surface as the one for the abstract backend, just to make it happy setSurface(m_outputs.first().eglSurface); return makeContextCurrent(m_outputs.first()); } void EglGbmBackend::createOutput(DrmOutput *drmOutput) { Output o; o.output = drmOutput; auto size = drmOutput->pixelSize(); - o.gbmSurface = gbm_surface_create(m_backend->gbmDevice(), size.width(), size.height(), + o.gbmSurface = std::make_shared(m_backend->gbmDevice(), size.width(), size.height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); if (!o.gbmSurface) { qCCritical(KWIN_DRM) << "Create gbm surface failed"; return; } - o.eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *)o.gbmSurface, nullptr); + o.eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *)((gbm_surface*)o.gbmSurface.get()), nullptr); if (o.eglSurface == EGL_NO_SURFACE) { qCCritical(KWIN_DRM) << "Create Window Surface failed"; - gbm_surface_destroy(o.gbmSurface); + o.gbmSurface.reset(); return; } m_outputs << o; } bool EglGbmBackend::makeContextCurrent(const Output &output) { const EGLSurface surface = output.eglSurface; if (surface == EGL_NO_SURFACE) { return false; } if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) { qCCritical(KWIN_DRM) << "Make Context Current failed"; return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_DRM) << "Error occurred while creating context " << error; return false; } // TODO: ensure the viewport is set correctly each time const QSize &overall = screens()->size(); const QRect &v = output.output->geometry(); // TODO: are the values correct? qreal scale = output.output->scale(); glViewport(-v.x() * scale, (v.height() - overall.height() - v.y()) * scale, overall.width() * scale, overall.height() * scale); return true; } bool EglGbmBackend::initBufferConfigs() { const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, }; EGLint count; EGLConfig configs[1024]; if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1, &count) == EGL_FALSE) { qCCritical(KWIN_DRM) << "choose config failed"; return false; } if (count != 1) { qCCritical(KWIN_DRM) << "choose config did not return a config" << count; return false; } setConfig(configs[0]); return true; } void EglGbmBackend::present() { for (auto &o: m_outputs) { makeContextCurrent(o); presentOnOutput(o); } } void EglGbmBackend::presentOnOutput(EglGbmBackend::Output &o) { eglSwapBuffers(eglDisplay(), o.eglSurface); o.buffer = m_backend->createBuffer(o.gbmSurface); m_backend->present(o.buffer, o.output); if (supportsBufferAge()) { eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); } } void EglGbmBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // TODO, create new buffer? } SceneOpenGL::TexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new EglGbmTexture(texture, this); } QRegion EglGbmBackend::prepareRenderingFrame() { startRenderTimer(); return QRegion(); } QRegion EglGbmBackend::prepareRenderingForScreen(int screenId) { const Output &o = m_outputs.at(screenId); makeContextCurrent(o); if (supportsBufferAge()) { QRegion region; // Note: An age of zero means the buffer contents are undefined if (o.bufferAge > 0 && o.bufferAge <= o.damageHistory.count()) { for (int i = 0; i < o.bufferAge - 1; i++) region |= o.damageHistory[i]; } else { region = o.output->geometry(); } return region; } return QRegion(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) Q_UNUSED(damagedRegion) } void EglGbmBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) { Output &o = m_outputs[screenId]; if (damagedRegion.intersected(o.output->geometry()).isEmpty() && screenId == 0) { // 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.intersected(o.output->geometry()).isEmpty()) glFlush(); for (auto &o: m_outputs) { o.bufferAge = 1; } return; } presentOnOutput(o); // Save the damaged region to history // Note: damage history is only collected for the first screen. For any other screen full repaints // are triggered. This is due to a limitation in Scene::paintGenericScreen which resets the Toplevel's // repaint. So multiple calls to Scene::paintScreen as it's done in multi-output rendering only // have correct damage information for the first screen. If we try to track damage nevertheless, // it creates artifacts. So for the time being we work around the problem by only supporting buffer // age on the first output. To properly support buffer age on all outputs the rendering needs to // be refactored in general. if (supportsBufferAge() && screenId == 0) { if (o.damageHistory.count() > 10) { o.damageHistory.removeLast(); } o.damageHistory.prepend(damagedRegion.intersected(o.output->geometry())); } } bool EglGbmBackend::usesOverlayWindow() const { return false; } bool EglGbmBackend::perScreenRendering() const { return true; } /************************************************ * EglTexture ************************************************/ EglGbmTexture::EglGbmTexture(KWin::SceneOpenGL::Texture *texture, EglGbmBackend *backend) : AbstractEglTexture(texture, backend) { } EglGbmTexture::~EglGbmTexture() = default; } // namespace diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h index 30e17e682..5ace7c6a9 100644 --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -1,95 +1,98 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_EGL_GBM_BACKEND_H #define KWIN_EGL_GBM_BACKEND_H #include "abstract_egl_backend.h" #include "scene_opengl.h" +#include + struct gbm_surface; namespace KWin { class DrmBackend; class DrmBuffer; class DrmOutput; +class GbmSurface; /** * @brief OpenGL Backend using Egl on a GBM surface. **/ class EglGbmBackend : public AbstractEglBackend { Q_OBJECT public: EglGbmBackend(DrmBackend *b); virtual ~EglGbmBackend(); void screenGeometryChanged(const QSize &size) override; SceneOpenGL::TexturePrivate *createBackendTexture(SceneOpenGL::Texture *texture) override; QRegion prepareRenderingFrame() override; void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; bool usesOverlayWindow() const override; bool perScreenRendering() const override; QRegion prepareRenderingForScreen(int screenId) override; void init() override; protected: void present() override; void cleanupSurfaces() override; private: bool initializeEgl(); bool initBufferConfigs(); bool initRenderingContext(); struct Output { DrmOutput *output = nullptr; DrmBuffer *buffer = nullptr; - gbm_surface *gbmSurface = nullptr; + std::shared_ptr gbmSurface; EGLSurface eglSurface = EGL_NO_SURFACE; int bufferAge = 0; /** * @brief The damage history for the past 10 frames. */ QList damageHistory; }; bool makeContextCurrent(const Output &output); void presentOnOutput(Output &output); void cleanupOutput(const Output &output); void createOutput(DrmOutput *output); DrmBackend *m_backend; QVector m_outputs; friend class EglGbmTexture; }; /** * @brief Texture using an EGLImageKHR. **/ class EglGbmTexture : public AbstractEglTexture { public: virtual ~EglGbmTexture(); private: friend class EglGbmBackend; EglGbmTexture(SceneOpenGL::Texture *texture, EglGbmBackend *backend); }; } // namespace #endif diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/plugins/platforms/drm/gbm_surface.cpp similarity index 52% copy from plugins/platforms/drm/drm_buffer_gbm.h copy to plugins/platforms/drm/gbm_surface.cpp index 82224b116..30eb988fc 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/plugins/platforms/drm/gbm_surface.cpp @@ -1,61 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright 2017 Roman Gilg -Copyright 2015 Martin Gräßlin +Copyright (C) 2017 Martin Flöser 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_DRM_BUFFER_GBM_H -#define KWIN_DRM_BUFFER_GBM_H +#include "gbm_surface.h" -#include "drm_buffer.h" - -struct gbm_bo; -struct gbm_surface; +#include namespace KWin { -class DrmBackend; +GbmSurface::GbmSurface(gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, uint32_t flags) + : m_surface(gbm_surface_create(gbm, width, height, format, flags)) +{ +} -class DrmSurfaceBuffer : public DrmBuffer +GbmSurface::~GbmSurface() { -public: - DrmSurfaceBuffer(DrmBackend *backend, gbm_surface *surface); - ~DrmSurfaceBuffer(); - - bool needsModeChange(DrmBuffer *b) const override { - if (DrmSurfaceBuffer *sb = dynamic_cast(b)) { - return hasBo() != sb->hasBo(); - } else { - return true; - } + if (m_surface) { + gbm_surface_destroy(m_surface); } +} - bool hasBo() const { - return m_bo != nullptr; +gbm_bo *GbmSurface::lockFrontBuffer() +{ + if (!m_surface) { + return nullptr; } - void releaseGbm() override; - -private: - gbm_surface *m_surface = nullptr; - gbm_bo *m_bo = nullptr; -}; - + return gbm_surface_lock_front_buffer(m_surface); } -#endif +void GbmSurface::releaseBuffer(gbm_bo *bo) +{ + if (!bo || !m_surface) { + return; + } + gbm_surface_release_buffer(m_surface, bo); +} +} diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/plugins/platforms/drm/gbm_surface.h similarity index 56% copy from plugins/platforms/drm/drm_buffer_gbm.h copy to plugins/platforms/drm/gbm_surface.h index 82224b116..6877a926c 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/plugins/platforms/drm/gbm_surface.h @@ -1,61 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright 2017 Roman Gilg -Copyright 2015 Martin Gräßlin +Copyright (C) 2017 Martin Flöser 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_DRM_BUFFER_GBM_H -#define KWIN_DRM_BUFFER_GBM_H +#ifndef KWIN_DRM_GBM_SURFACE_H +#define KWIN_DRM_GBM_SURFACE_H -#include "drm_buffer.h" +#include struct gbm_bo; +struct gbm_device; struct gbm_surface; namespace KWin { -class DrmBackend; - -class DrmSurfaceBuffer : public DrmBuffer +class GbmSurface { public: - DrmSurfaceBuffer(DrmBackend *backend, gbm_surface *surface); - ~DrmSurfaceBuffer(); - - bool needsModeChange(DrmBuffer *b) const override { - if (DrmSurfaceBuffer *sb = dynamic_cast(b)) { - return hasBo() != sb->hasBo(); - } else { - return true; - } + explicit GbmSurface(gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, uint32_t flags); + ~GbmSurface(); + + gbm_bo *lockFrontBuffer(); + void releaseBuffer(gbm_bo *bo); + + operator bool() const { + return m_surface != nullptr; } - bool hasBo() const { - return m_bo != nullptr; + operator gbm_surface*() const { + return m_surface; } - void releaseGbm() override; private: - gbm_surface *m_surface = nullptr; - gbm_bo *m_bo = nullptr; + gbm_surface *m_surface; }; } #endif -