diff --git a/CMakeLists.txt b/CMakeLists.txt index e3838fec94..70c397b266 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,6 +274,7 @@ find_package(XCB 1.10 REQUIRED COMPONENTS COMPOSITE CURSOR DAMAGE + DRI3 GLX ICCCM IMAGE diff --git a/src/backends/x11/windowed/CMakeLists.txt b/src/backends/x11/windowed/CMakeLists.txt index be3c5326fd..722431fb27 100644 --- a/src/backends/x11/windowed/CMakeLists.txt +++ b/src/backends/x11/windowed/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources(kwin PRIVATE target_link_libraries(kwin X11::XCB X11::X11 + XCB::DRI3 XCB::PRESENT ) if (X11_Xi_FOUND) diff --git a/src/backends/x11/windowed/x11_windowed_backend.cpp b/src/backends/x11/windowed/x11_windowed_backend.cpp index 432e887f5a..8a2a04db38 100644 --- a/src/backends/x11/windowed/x11_windowed_backend.cpp +++ b/src/backends/x11/windowed/x11_windowed_backend.cpp @@ -25,6 +25,7 @@ #include #include // xcb +#include #include #include #include @@ -38,6 +39,9 @@ // system #include #include +#include +#include +#include #include namespace KWin @@ -169,6 +173,11 @@ X11WindowedBackend::~X11WindowedBackend() if (sceneEglDisplay() != EGL_NO_DISPLAY) { eglTerminate(sceneEglDisplay()); } + + if (m_gbmDevice) { + gbm_device_destroy(m_gbmDevice); + } + if (m_connection) { if (m_keySymbols) { xcb_key_symbols_free(m_keySymbols); @@ -231,7 +240,22 @@ bool X11WindowedBackend::initialize() } } + const xcb_query_extension_reply_t *driExtension = xcb_get_extension_data(m_connection, &xcb_dri3_id); + if (driExtension && driExtension->present) { + xcb_dri3_query_version_cookie_t cookie = xcb_dri3_query_version(m_connection, 1, 2); + UniqueCPtr reply(xcb_dri3_query_version_reply(m_connection, cookie, nullptr)); + if (reply) { + m_hasDri = true; + m_driMajorVersion = reply->major_version; + m_driMinorVersion = reply->minor_version; + } else { + qCWarning(KWIN_X11WINDOWED) << "Requested DRI3 extension version is unsupported"; + } + } + initXInput(); + initDri3(); + XRenderUtils::init(m_connection, m_screen->root); createOutputs(); @@ -288,6 +312,46 @@ void X11WindowedBackend::initXInput() #endif } +void X11WindowedBackend::initDri3() +{ + if (m_hasDri) { + xcb_dri3_open_cookie_t cookie = xcb_dri3_open(m_connection, m_screen->root, 0); + UniqueCPtr reply(xcb_dri3_open_reply(m_connection, cookie, nullptr)); + if (reply && reply->nfd == 1) { + int fd = xcb_dri3_open_reply_fds(m_connection, reply.get())[0]; + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + m_drmFileDescriptor = FileDescriptor{fd}; + m_gbmDevice = gbm_create_device(m_drmFileDescriptor.get()); + } + } + + xcb_depth_iterator_t it = xcb_screen_allowed_depths_iterator(m_screen); + while (it.rem > 0) { + uint32_t format = driFormatForDepth(it.data->depth); + if (format) { + QVector &mods = m_driFormats[format]; + + if (m_driMajorVersion > 1 || m_driMinorVersion >= 2) { + xcb_dri3_get_supported_modifiers_cookie_t cookie = xcb_dri3_get_supported_modifiers(m_connection, m_screen->root, it.data->depth, 32); + UniqueCPtr reply(xcb_dri3_get_supported_modifiers_reply(m_connection, cookie, nullptr)); + if (reply) { + const uint64_t *modifiers = xcb_dri3_get_supported_modifiers_screen_modifiers(reply.get()); + const int modifierCount = xcb_dri3_get_supported_modifiers_screen_modifiers_length(reply.get()); + for (int i = 0; i < modifierCount; ++i) { + mods.append(modifiers[i]); + } + } + } + + if (mods.isEmpty()) { + mods.append(DRM_FORMAT_MOD_INVALID); + } + } + + xcb_depth_next(&it); + } +} + X11WindowedOutput *X11WindowedBackend::findOutput(xcb_window_t window) const { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), @@ -625,6 +689,11 @@ xcb_window_t X11WindowedBackend::rootWindow() const return m_screen->root; } +gbm_device *X11WindowedBackend::gbmDevice() const +{ + return m_gbmDevice; +} + X11WindowedInputDevice *X11WindowedBackend::pointerDevice() const { return m_pointerDevice.get(); @@ -680,9 +749,39 @@ bool X11WindowedBackend::hasXInput() const return m_hasXInput; } +QHash> X11WindowedBackend::driFormats() const +{ + return m_driFormats; +} + +uint32_t X11WindowedBackend::driFormatForDepth(int depth) const +{ + switch (depth) { + case 24: + return DRM_FORMAT_XRGB8888; + case 32: + return DRM_FORMAT_ARGB8888; + default: + return 0; + } +} + +int X11WindowedBackend::driMajorVersion() const +{ + return m_driMajorVersion; +} + +int X11WindowedBackend::driMinorVersion() const +{ + return m_driMinorVersion; +} + QVector X11WindowedBackend::supportedCompositors() const { - QVector ret{OpenGLCompositing}; + QVector ret; + if (m_gbmDevice) { + ret.append(OpenGLCompositing); + } if (m_hasShm) { ret.append(QPainterCompositing); } diff --git a/src/backends/x11/windowed/x11_windowed_backend.h b/src/backends/x11/windowed/x11_windowed_backend.h index d82c3bf2d5..db24f8aa1b 100644 --- a/src/backends/x11/windowed/x11_windowed_backend.h +++ b/src/backends/x11/windowed/x11_windowed_backend.h @@ -11,6 +11,7 @@ #include "core/inputbackend.h" #include "core/inputdevice.h" #include "core/outputbackend.h" +#include "utils/filedescriptor.h" #include @@ -19,6 +20,7 @@ #include +struct gbm_device; struct _XDisplay; typedef struct _XDisplay Display; typedef struct _XCBKeySymbols xcb_key_symbols_t; @@ -102,9 +104,15 @@ public: xcb_screen_t *screen() const; int screenNumer() const; xcb_window_t rootWindow() const; + gbm_device *gbmDevice() const; bool hasXInput() const; + QHash> driFormats() const; + uint32_t driFormatForDepth(int depth) const; + int driMajorVersion() const; + int driMinorVersion() const; + bool initialize() override; std::unique_ptr createOpenGLBackend() override; std::unique_ptr createQPainterBackend() override; @@ -128,6 +136,7 @@ private: void handlePresentEvent(xcb_ge_generic_event_t *event); void updateSize(xcb_configure_notify_event_t *event); void initXInput(); + void initDri3(); X11WindowedOutput *findOutput(xcb_window_t window) const; void destroyOutputs(); @@ -158,6 +167,14 @@ private: bool m_hasShm = false; + bool m_hasDri = false; + int m_driMajorVersion = 0; + int m_driMinorVersion = 0; + QHash> m_driFormats; + + FileDescriptor m_drmFileDescriptor; + gbm_device *m_gbmDevice = nullptr; + QVector m_outputs; }; diff --git a/src/backends/x11/windowed/x11_windowed_egl_backend.cpp b/src/backends/x11/windowed/x11_windowed_egl_backend.cpp index 57c7c284b9..bdd13b39ed 100644 --- a/src/backends/x11/windowed/x11_windowed_egl_backend.cpp +++ b/src/backends/x11/windowed/x11_windowed_egl_backend.cpp @@ -7,70 +7,202 @@ SPDX-License-Identifier: GPL-2.0-or-later */ #include "x11_windowed_egl_backend.h" -// kwin +#include "x11_windowed_backend.h" +#include "x11_windowed_logging.h" +#include "x11_windowed_output.h" +#include "../../drm/gbm_dmabuf.h" + #include "basiceglsurfacetexture_internal.h" #include "basiceglsurfacetexture_wayland.h" -#include "x11_windowed_backend.h" -#include "x11_windowed_output.h" -// kwin libs + #include -#include +#include +#include namespace KWin { -X11WindowedEglPrimaryLayer::X11WindowedEglPrimaryLayer(X11WindowedEglBackend *backend, X11WindowedOutput *output, EGLSurface surface) - : m_eglSurface(surface) - , m_output(output) +X11WindowedEglLayerBuffer::X11WindowedEglLayerBuffer(const QSize &size, uint32_t format, uint32_t depth, uint32_t bpp, const QVector &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend) + : m_backend(backend) +{ + X11WindowedBackend *x11Backend = backend->backend(); + + m_bo = createGbmBo(x11Backend->gbmDevice(), size, format, modifiers); + if (!m_bo) { + qCCritical(KWIN_X11WINDOWED) << "Failed to allocate a buffer for an output layer"; + return; + } + + const DmaBufAttributes attributes = dmaBufAttributesForBo(m_bo); + + m_pixmap = xcb_generate_id(x11Backend->connection()); + if (x11Backend->driMajorVersion() >= 1 || x11Backend->driMinorVersion() >= 2) { + // xcb_dri3_pixmap_from_buffers() takes the ownership of the file descriptors. + int fds[4] = { + attributes.fd[0].duplicate().take(), + attributes.fd[1].duplicate().take(), + attributes.fd[2].duplicate().take(), + attributes.fd[3].duplicate().take(), + }; + xcb_dri3_pixmap_from_buffers(x11Backend->connection(), m_pixmap, drawable, attributes.planeCount, + size.width(), size.height(), + attributes.pitch[0], attributes.offset[0], + attributes.pitch[1], attributes.offset[1], + attributes.pitch[2], attributes.offset[2], + attributes.pitch[3], attributes.offset[3], + depth, bpp, attributes.modifier, fds); + } else { + // xcb_dri3_pixmap_from_buffer() takes the ownership of the file descriptor. + xcb_dri3_pixmap_from_buffer(x11Backend->connection(), m_pixmap, drawable, + size.height() * attributes.pitch[0], size.width(), size.height(), + attributes.pitch[0], depth, bpp, attributes.fd[0].duplicate().take()); + } + + m_texture = backend->importDmaBufAsTexture(attributes); + m_framebuffer = std::make_unique(m_texture.get()); +} + +X11WindowedEglLayerBuffer::~X11WindowedEglLayerBuffer() +{ + m_texture.reset(); + m_framebuffer.reset(); + + if (m_pixmap) { + xcb_free_pixmap(m_backend->backend()->connection(), m_pixmap); + } + if (m_bo) { + gbm_bo_destroy(m_bo); + } +} + +xcb_pixmap_t X11WindowedEglLayerBuffer::pixmap() const +{ + return m_pixmap; +} + +std::shared_ptr X11WindowedEglLayerBuffer::texture() const +{ + return m_texture; +} + +GLFramebuffer *X11WindowedEglLayerBuffer::framebuffer() const +{ + return m_framebuffer.get(); +} + +int X11WindowedEglLayerBuffer::age() const +{ + return m_age; +} + +X11WindowedEglLayerSwapchain::X11WindowedEglLayerSwapchain(const QSize &size, uint32_t format, uint32_t depth, uint32_t bpp, const QVector &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend) + : m_backend(backend) + , m_size(size) +{ + for (int i = 0; i < 2; ++i) { + m_buffers.append(std::make_shared(size, format, depth, bpp, modifiers, drawable, backend)); + } +} + +X11WindowedEglLayerSwapchain::~X11WindowedEglLayerSwapchain() +{ +} + +QSize X11WindowedEglLayerSwapchain::size() const +{ + return m_size; +} + +std::shared_ptr X11WindowedEglLayerSwapchain::acquire() +{ + m_index = (m_index + 1) % m_buffers.count(); + return m_buffers[m_index]; +} + +void X11WindowedEglLayerSwapchain::release(std::shared_ptr buffer) +{ + Q_ASSERT(m_buffers[m_index] == buffer); + + for (qsizetype i = 0; i < m_buffers.count(); ++i) { + if (m_buffers[i] == buffer) { + m_buffers[i]->m_age = 1; + } else if (m_buffers[i]->m_age > 0) { + m_buffers[i]->m_age++; + } + } +} + +X11WindowedEglPrimaryLayer::X11WindowedEglPrimaryLayer(X11WindowedEglBackend *backend, X11WindowedOutput *output) + : m_output(output) , m_backend(backend) { } -X11WindowedEglPrimaryLayer::~X11WindowedEglPrimaryLayer() -{ - eglDestroySurface(m_backend->eglDisplay(), m_eglSurface); -} - -void X11WindowedEglPrimaryLayer::ensureFbo() -{ - if (!m_fbo || m_fbo->size() != m_output->pixelSize()) { - m_fbo = std::make_unique(0, m_output->pixelSize()); - } -} - std::optional X11WindowedEglPrimaryLayer::beginFrame() { - eglMakeCurrent(m_backend->eglDisplay(), m_eglSurface, m_eglSurface, m_backend->context()); - ensureFbo(); + eglMakeCurrent(m_backend->eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, m_backend->context()); + + const QSize bufferSize = m_output->pixelSize(); + if (!m_swapchain || m_swapchain->size() != bufferSize) { + const uint32_t format = DRM_FORMAT_XRGB8888; + const QHash> formatTable = m_backend->backend()->driFormats(); + if (!formatTable.contains(format)) { + return std::nullopt; + } + m_swapchain = std::make_unique(bufferSize, format, 24, 32, formatTable[format], m_output->window(), m_backend); + } + + m_buffer = m_swapchain->acquire(); QRegion repaint = m_output->exposedArea() + m_output->rect(); m_output->clearExposedArea(); + GLFramebuffer::pushFramebuffer(m_buffer->framebuffer()); return OutputLayerBeginFrameInfo{ - .renderTarget = RenderTarget(m_fbo.get()), + .renderTarget = RenderTarget(m_buffer->framebuffer()), .repaint = repaint, }; } bool X11WindowedEglPrimaryLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { - m_lastDamage = damagedRegion; return true; } -EGLSurface X11WindowedEglPrimaryLayer::surface() const +void X11WindowedEglPrimaryLayer::present() { - return m_eglSurface; + xcb_xfixes_region_t valid = 0; + xcb_xfixes_region_t update = 0; + uint32_t serial = 0; + uint32_t options = 0; + uint64_t targetMsc = 0; + + xcb_present_pixmap(m_output->backend()->connection(), + m_output->window(), + m_buffer->pixmap(), + serial, + valid, + update, + 0, + 0, + XCB_NONE, + XCB_NONE, + XCB_NONE, + options, + targetMsc, + 0, + 0, + 0, + nullptr); + + Q_EMIT m_output->outputChange(infiniteRegion()); + + m_swapchain->release(m_buffer); } -QRegion X11WindowedEglPrimaryLayer::lastDamage() const +std::shared_ptr X11WindowedEglPrimaryLayer::texture() const { - return m_lastDamage; -} - -GLFramebuffer *X11WindowedEglPrimaryLayer::fbo() const -{ - return m_fbo.get(); + return m_buffer->texture(); } quint32 X11WindowedEglPrimaryLayer::format() const @@ -137,6 +269,11 @@ X11WindowedEglBackend::~X11WindowedEglBackend() cleanup(); } +X11WindowedBackend *X11WindowedEglBackend::backend() const +{ + return m_backend; +} + void X11WindowedEglBackend::init() { EglOnXBackend::init(); @@ -156,41 +293,17 @@ bool X11WindowedEglBackend::createSurfaces() const auto &outputs = m_backend->outputs(); for (const auto &output : outputs) { X11WindowedOutput *x11Output = static_cast(output); - EGLSurface s = createSurface(x11Output->window()); - if (s == EGL_NO_SURFACE) { - return false; - } m_outputs[output] = Layers{ - .primaryLayer = std::make_unique(this, x11Output, s), + .primaryLayer = std::make_unique(this, x11Output), .cursorLayer = std::make_unique(this, x11Output), }; } - if (m_outputs.empty()) { - return false; - } return true; } void X11WindowedEglBackend::present(Output *output) { - const auto &renderOutput = m_outputs[output]; - presentSurface(renderOutput.primaryLayer->surface(), renderOutput.primaryLayer->lastDamage(), output->geometry()); - Q_EMIT output->outputChange(renderOutput.primaryLayer->lastDamage()); -} - -void X11WindowedEglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry) -{ - const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry); - - if (fullRepaint || !havePostSubBuffer()) { - // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation) - eglSwapBuffers(eglDisplay(), surface); - } else { - // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area - for (const QRect &r : damage) { - eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height()); - } - } + m_outputs[output].primaryLayer->present(); } OutputLayer *X11WindowedEglBackend::primaryLayer(Output *output) @@ -220,10 +333,7 @@ std::shared_ptr X11WindowedEglBackend::textureForOutput(Output *outpu return nullptr; } - GLFramebuffer::pushFramebuffer(it->second.primaryLayer->fbo()); - auto ret = AbstractEglBackend::textureForOutput(output); - GLFramebuffer::popFramebuffer(); - return ret; + return it->second.primaryLayer->texture(); } } // namespace diff --git a/src/backends/x11/windowed/x11_windowed_egl_backend.h b/src/backends/x11/windowed/x11_windowed_egl_backend.h index d5da09cf21..43b3fa509f 100644 --- a/src/backends/x11/windowed/x11_windowed_egl_backend.h +++ b/src/backends/x11/windowed/x11_windowed_egl_backend.h @@ -14,6 +14,8 @@ #include +struct gbm_bo; + namespace KWin { @@ -21,26 +23,60 @@ class X11WindowedBackend; class X11WindowedOutput; class X11WindowedEglBackend; +class X11WindowedEglLayerBuffer +{ +public: + X11WindowedEglLayerBuffer(const QSize &size, uint32_t format, uint32_t depth, uint32_t bpp, const QVector &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend); + ~X11WindowedEglLayerBuffer(); + + xcb_pixmap_t pixmap() const; + std::shared_ptr texture() const; + GLFramebuffer *framebuffer() const; + int age() const; + +private: + X11WindowedEglBackend *m_backend; + xcb_pixmap_t m_pixmap = XCB_PIXMAP_NONE; + gbm_bo *m_bo = nullptr; + std::unique_ptr m_framebuffer; + std::shared_ptr m_texture; + int m_age = 0; + friend class X11WindowedEglLayerSwapchain; +}; + +class X11WindowedEglLayerSwapchain +{ +public: + X11WindowedEglLayerSwapchain(const QSize &size, uint32_t format, uint32_t depth, uint32_t bpp, const QVector &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend); + ~X11WindowedEglLayerSwapchain(); + + QSize size() const; + + std::shared_ptr acquire(); + void release(std::shared_ptr buffer); + +private: + X11WindowedEglBackend *m_backend; + QSize m_size; + QVector> m_buffers; + int m_index = 0; +}; + class X11WindowedEglPrimaryLayer : public OutputLayer { public: - X11WindowedEglPrimaryLayer(X11WindowedEglBackend *backend, X11WindowedOutput *output, EGLSurface surface); - ~X11WindowedEglPrimaryLayer(); + X11WindowedEglPrimaryLayer(X11WindowedEglBackend *backend, X11WindowedOutput *output); std::optional beginFrame() override; bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; quint32 format() const override; - EGLSurface surface() const; - QRegion lastDamage() const; - GLFramebuffer *fbo() const; + + std::shared_ptr texture() const; + void present(); private: - void ensureFbo(); - - EGLSurface m_eglSurface; - std::unique_ptr m_fbo; - QRegion m_lastDamage; - + std::unique_ptr m_swapchain; + std::shared_ptr m_buffer; X11WindowedOutput *const m_output; X11WindowedEglBackend *const m_backend; }; @@ -75,6 +111,8 @@ public: explicit X11WindowedEglBackend(X11WindowedBackend *backend); ~X11WindowedEglBackend() override; + X11WindowedBackend *backend() const; + std::unique_ptr createSurfaceTextureInternal(SurfacePixmapInternal *pixmap) override; std::unique_ptr createSurfaceTextureWayland(SurfacePixmapWayland *pixmap) override; std::shared_ptr textureForOutput(Output *output) const override; @@ -89,8 +127,6 @@ protected: bool createSurfaces() override; private: - void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry); - struct Layers { std::unique_ptr primaryLayer; diff --git a/src/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/src/platformsupport/scenes/opengl/abstract_egl_backend.cpp index 8f65533de7..7390b2c1bb 100644 --- a/src/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/src/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -367,14 +367,6 @@ void AbstractEglBackend::setSurface(const EGLSurface &surface) m_surface = surface; } -std::shared_ptr AbstractEglBackend::textureForOutput(Output *requestedOutput) const -{ - std::shared_ptr texture(new GLTexture(GL_RGBA8, requestedOutput->pixelSize())); - GLFramebuffer renderTarget(texture.get()); - renderTarget.blitFromFramebuffer(QRect(0, texture->height(), texture->width(), -texture->height())); - return texture; -} - dev_t AbstractEglBackend::deviceId() const { return m_deviceId; diff --git a/src/platformsupport/scenes/opengl/abstract_egl_backend.h b/src/platformsupport/scenes/opengl/abstract_egl_backend.h index 5bab630500..1427c1a130 100644 --- a/src/platformsupport/scenes/opengl/abstract_egl_backend.h +++ b/src/platformsupport/scenes/opengl/abstract_egl_backend.h @@ -62,7 +62,6 @@ public: return m_config; } - std::shared_ptr textureForOutput(Output *output) const override; QHash> supportedFormats() const override; dev_t deviceId() const; diff --git a/src/utils/filedescriptor.cpp b/src/utils/filedescriptor.cpp index b854e80cef..b5e98c869c 100644 --- a/src/utils/filedescriptor.cpp +++ b/src/utils/filedescriptor.cpp @@ -50,6 +50,11 @@ int FileDescriptor::get() const return m_fd; } +int FileDescriptor::take() +{ + return std::exchange(m_fd, -1); +} + FileDescriptor FileDescriptor::duplicate() const { if (m_fd != -1) { diff --git a/src/utils/filedescriptor.h b/src/utils/filedescriptor.h index 308f5e0b67..1b0647c7ea 100644 --- a/src/utils/filedescriptor.h +++ b/src/utils/filedescriptor.h @@ -24,6 +24,7 @@ public: bool isValid() const; int get() const; + int take(); FileDescriptor duplicate() const; private: