Commit 9c20df50 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii

screencast: Use fences to avoid stalling the graphics pipeline

Currently, we use glFinish() to ensure that stream consumers don't see
corrupted or rather incomplete buffers. This is a serious issue because
glFinish() not only prevents the gpu from processing new GL commands,
but it also blocks the compositor.

This change addresses the blocking issue by using native fences. With
the proposed change, after finishing recording a frame, a fence is
inserted in the command stream. When the native fence is signaled, the
pending pipewire buffer will be enqueued.

If the EGL_ANDROID_native_fence_sync extension is not supported, we'll
fall back to using glFinish().
parent 170c4168
......@@ -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)
......
......@@ -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;
......
......@@ -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.
*/
......
......@@ -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;
}
......
......@@ -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.
*/
......
......@@ -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);
......
......@@ -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;
......
......@@ -686,6 +686,11 @@ bool Scene::supportsSurfacelessContext() const
return false;
}
bool Scene::supportsNativeFence() const
{
return false;
}
void Scene::triggerFence()
{
}
......
......@@ -140,6 +140,7 @@ public:
virtual bool makeOpenGLContextCurrent();
virtual void doneOpenGLContextCurrent();
virtual bool supportsSurfacelessContext() const;
virtual bool supportsNativeFence() const;
virtual QMatrix4x4 screenProjectionMatrix() const;
......
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglnativefence.h"
#include <unistd.h>
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
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <epoxy/egl.h>
#include <fixx11h.h>
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
......@@ -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
......
......@@ -17,6 +17,7 @@
#include <QObject>
#include <QSharedPointer>
#include <QSize>
#include <QSocketNotifier>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
......@@ -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<PipeWireCore> pwCore;
struct pw_stream *pwStream = nullptr;
......@@ -96,6 +100,10 @@ private:
QRect cursorGeometry(Cursor *cursor) const;
QHash<struct pw_buffer *, QSharedPointer<DmaBufTexture>> m_dmabufDataForPwBuffer;
pw_buffer *m_pendingBuffer = nullptr;
QSocketNotifier *m_pendingNotifier = nullptr;
EGLNativeFence *m_pendingFence = nullptr;
};
} // namespace KWin
......@@ -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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment