diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d07858987..87c7ed7f21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -777,6 +777,7 @@ add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS}) if (PipeWire_FOUND) target_sources(kwin_wayland PRIVATE + screencast/eglnativefence.cpp screencast/screencastmanager.cpp screencast/pipewirecore.cpp screencast/pipewirestream.cpp) diff --git a/platform.cpp b/platform.cpp index 98f8ef6fde..08e47609df 100644 --- a/platform.cpp +++ b/platform.cpp @@ -425,6 +425,14 @@ bool Platform::supportsSurfacelessContext() const return false; } +bool Platform::supportsNativeFence() const +{ + if (Compositor *compositor = Compositor::self()) { + return compositor->scene()->supportsNativeFence(); + } + return false; +} + EGLDisplay KWin::Platform::sceneEglDisplay() const { return m_eglDisplay; diff --git a/platform.h b/platform.h index 506801af3d..3c02afd455 100644 --- a/platform.h +++ b/platform.h @@ -101,6 +101,10 @@ public: * so that a sharing context could be created. */ bool supportsSurfacelessContext() const; + /** + * Whether our Compositing EGL display supports creating native EGL fences. + */ + bool supportsNativeFence() const; /** * The EGLDisplay used by the compositing scene. */ diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp index abcc7ec9f4..36feab1070 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -154,6 +154,7 @@ bool AbstractEglBackend::initEglAPI() const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS); setExtensions(eglExtensions.split(' ')); setSupportsSurfacelessContext(hasExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context"))); + setSupportsNativeFence(hasExtension(QByteArrayLiteral("EGL_ANDROID_native_fence_sync"))); return true; } diff --git a/platformsupport/scenes/opengl/backend.h b/platformsupport/scenes/opengl/backend.h index c7fdb564de..6f50d2c890 100644 --- a/platformsupport/scenes/opengl/backend.h +++ b/platformsupport/scenes/opengl/backend.h @@ -170,6 +170,10 @@ public: { return m_haveSurfacelessContext; } + bool supportsNativeFence() const + { + return m_haveNativeFence; + } /** * Returns the damage that has accumulated since a buffer of the given age was presented. @@ -270,6 +274,11 @@ protected: m_haveSurfacelessContext = value; } + void setSupportsNativeFence(bool value) + { + m_haveNativeFence = value; + } + /** * @return const QRegion& Damage of previously rendered frame */ @@ -323,6 +332,10 @@ private: * @brief Whether the backend supports EGL_KHR_surfaceless_context. */ bool m_haveSurfacelessContext = false; + /** + * @brief Whether the backend supports EGL_ANDROID_native_fence_sync. + */ + bool m_haveNativeFence = false; /** * @brief Whether the initialization failed, of course default to @c false. */ diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index 79f3445784..7cfbf91d18 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -865,6 +865,11 @@ bool SceneOpenGL::supportsSurfacelessContext() const return m_backend->supportsSurfacelessContext(); } +bool SceneOpenGL::supportsNativeFence() const +{ + return m_backend->supportsNativeFence(); +} + Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame) { return new SceneOpenGL::EffectFrame(frame, this); diff --git a/plugins/scenes/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h index d2c281b84a..60dca99e82 100644 --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -46,6 +46,7 @@ public: bool makeOpenGLContextCurrent() override; void doneOpenGLContextCurrent() override; bool supportsSurfacelessContext() const override; + bool supportsNativeFence() const override; Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override; void triggerFence() override; virtual QMatrix4x4 projectionMatrix() const = 0; diff --git a/scene.cpp b/scene.cpp index 7b3373e992..fe4105cfc3 100644 --- a/scene.cpp +++ b/scene.cpp @@ -686,6 +686,11 @@ bool Scene::supportsSurfacelessContext() const return false; } +bool Scene::supportsNativeFence() const +{ + return false; +} + void Scene::triggerFence() { } diff --git a/scene.h b/scene.h index 6f846a99b7..fc25ae5cc9 100644 --- a/scene.h +++ b/scene.h @@ -140,6 +140,7 @@ public: virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent(); virtual bool supportsSurfacelessContext() const; + virtual bool supportsNativeFence() const; virtual QMatrix4x4 screenProjectionMatrix() const; diff --git a/screencast/eglnativefence.cpp b/screencast/eglnativefence.cpp new file mode 100644 index 0000000000..c10afc3e5c --- /dev/null +++ b/screencast/eglnativefence.cpp @@ -0,0 +1,50 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "eglnativefence.h" + +#include + +namespace KWin +{ + +#ifndef EGL_ANDROID_native_fence_sync +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 +#endif // EGL_ANDROID_native_fence_sync + +EGLNativeFence::EGLNativeFence(EGLDisplay display) + : m_display(display) +{ + m_sync = eglCreateSyncKHR(m_display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + if (m_sync != EGL_NO_SYNC_KHR) { + // The native fence will get a valid sync file fd only after a flush. + glFlush(); + m_fileDescriptor = eglDupNativeFenceFDANDROID(m_display, m_sync); + } +} + +EGLNativeFence::~EGLNativeFence() +{ + if (m_fileDescriptor != EGL_NO_NATIVE_FENCE_FD_ANDROID) { + close(m_fileDescriptor); + } + if (m_sync != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(m_display, m_sync); + } +} + +bool EGLNativeFence::isValid() const +{ + return m_sync != EGL_NO_SYNC_KHR && m_fileDescriptor != EGL_NO_NATIVE_FENCE_FD_ANDROID; +} + +int EGLNativeFence::fileDescriptor() const +{ + return m_fileDescriptor; +} + +} // namespace KWin diff --git a/screencast/eglnativefence.h b/screencast/eglnativefence.h new file mode 100644 index 0000000000..c8dfd278a0 --- /dev/null +++ b/screencast/eglnativefence.h @@ -0,0 +1,32 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +namespace KWin +{ + +class EGLNativeFence +{ +public: + explicit EGLNativeFence(EGLDisplay display); + ~EGLNativeFence(); + + bool isValid() const; + int fileDescriptor() const; + +private: + EGLSyncKHR m_sync = EGL_NO_SYNC_KHR; + EGLDisplay m_display = EGL_NO_DISPLAY; + int m_fileDescriptor = -1; + + Q_DISABLE_COPY(EGLNativeFence) +}; + +} // namespace KWin diff --git a/screencast/pipewirestream.cpp b/screencast/pipewirestream.cpp index 26779ebaf1..44f92c5eda 100644 --- a/screencast/pipewirestream.cpp +++ b/screencast/pipewirestream.cpp @@ -9,6 +9,7 @@ #include "pipewirestream.h" #include "cursor.h" #include "dmabuftexture.h" +#include "eglnativefence.h" #include "kwingltexture.h" #include "kwinglutils.h" #include "kwinscreencast_logging.h" @@ -298,6 +299,11 @@ void PipeWireStream::recordFrame(GLTexture *frameTexture, const QRegion &damaged Q_ASSERT(!m_stopped); Q_ASSERT(frameTexture); + if (m_pendingBuffer) { + qCWarning(KWIN_SCREENCAST) << "Dropping a screencast frame because the compositor is slow"; + return; + } + if (frameTexture->size() != m_resolution) { m_resolution = frameTexture->size(); newStreamParams(); @@ -409,7 +415,49 @@ void PipeWireStream::recordFrame(GLTexture *frameTexture, const QRegion &damaged (spa_meta_cursor *) spa_buffer_find_meta_data (spa_buffer, SPA_META_Cursor, sizeof (spa_meta_cursor))); } - pw_stream_queue_buffer(pwStream, buffer); + tryEnqueue(buffer); +} + +void PipeWireStream::tryEnqueue(pw_buffer *buffer) +{ + m_pendingBuffer = buffer; + + // The GPU doesn't necessarily process draw commands as soon as they are issued. Thus, + // we need to insert a fence into the command stream and enqueue the pipewire buffer + // only after the fence is signaled; otherwise stream consumers will most likely see + // a corrupted buffer. + if (kwinApp()->platform()->supportsNativeFence()) { + Q_ASSERT_X(eglGetCurrentContext(), "tryEnqueue", "no current context"); + m_pendingFence = new EGLNativeFence(kwinApp()->platform()->sceneEglDisplay()); + if (!m_pendingFence->isValid()) { + qCWarning(KWIN_SCREENCAST) << "Failed to create a native EGL fence"; + glFinish(); + enqueue(); + } else { + m_pendingNotifier = new QSocketNotifier(m_pendingFence->fileDescriptor(), + QSocketNotifier::Read, this); + connect(m_pendingNotifier, &QSocketNotifier::activated, this, &PipeWireStream::enqueue); + } + } else { + // The compositing backend doesn't support native fences. We don't have any other choice + // but stall the graphics pipeline. Otherwise stream consumers may see an incomplete buffer. + glFinish(); + enqueue(); + } +} + +void PipeWireStream::enqueue() +{ + Q_ASSERT_X(m_pendingBuffer, "enqueue", "pending buffer must be valid"); + + delete m_pendingFence; + delete m_pendingNotifier; + + pw_stream_queue_buffer(pwStream, m_pendingBuffer); + + m_pendingBuffer = nullptr; + m_pendingFence = nullptr; + m_pendingNotifier = nullptr; } QRect PipeWireStream::cursorGeometry(Cursor *cursor) const diff --git a/screencast/pipewirestream.h b/screencast/pipewirestream.h index b920c959fe..c1568623b8 100644 --- a/screencast/pipewirestream.h +++ b/screencast/pipewirestream.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ namespace KWin class Cursor; class DmaBufTexture; +class EGLNativeFence; class GLTexture; class PipeWireCore; @@ -68,6 +70,8 @@ private: void coreFailed(const QString &errorMessage); void sendCursorData(Cursor *cursor, spa_meta_cursor *spa_cursor); void newStreamParams(); + void tryEnqueue(pw_buffer *buffer); + void enqueue(); QSharedPointer pwCore; struct pw_stream *pwStream = nullptr; @@ -96,6 +100,10 @@ private: QRect cursorGeometry(Cursor *cursor) const; QHash> m_dmabufDataForPwBuffer; + + pw_buffer *m_pendingBuffer = nullptr; + QSocketNotifier *m_pendingNotifier = nullptr; + EGLNativeFence *m_pendingFence = nullptr; }; } // namespace KWin diff --git a/screencast/screencastmanager.cpp b/screencast/screencastmanager.cpp index 0cae792ea1..691a5efafe 100644 --- a/screencast/screencastmanager.cpp +++ b/screencast/screencastmanager.cpp @@ -78,7 +78,6 @@ private: recordFrame(frameTexture.data(), m_damagedRegion); frameTexture->setYInverted(wasYInverted); m_damagedRegion = {}; - glFinish(); // TODO: Don't stall the whole pipeline. Use EGL_ANDROID_native_fence_sync. } QRegion m_damagedRegion;