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().
This commit is contained in:
parent
170c41681d
commit
9c20df5030
14 changed files with 178 additions and 2 deletions
|
@ -777,6 +777,7 @@ add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS})
|
||||||
if (PipeWire_FOUND)
|
if (PipeWire_FOUND)
|
||||||
target_sources(kwin_wayland
|
target_sources(kwin_wayland
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
screencast/eglnativefence.cpp
|
||||||
screencast/screencastmanager.cpp
|
screencast/screencastmanager.cpp
|
||||||
screencast/pipewirecore.cpp
|
screencast/pipewirecore.cpp
|
||||||
screencast/pipewirestream.cpp)
|
screencast/pipewirestream.cpp)
|
||||||
|
|
|
@ -425,6 +425,14 @@ bool Platform::supportsSurfacelessContext() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Platform::supportsNativeFence() const
|
||||||
|
{
|
||||||
|
if (Compositor *compositor = Compositor::self()) {
|
||||||
|
return compositor->scene()->supportsNativeFence();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
EGLDisplay KWin::Platform::sceneEglDisplay() const
|
EGLDisplay KWin::Platform::sceneEglDisplay() const
|
||||||
{
|
{
|
||||||
return m_eglDisplay;
|
return m_eglDisplay;
|
||||||
|
|
|
@ -101,6 +101,10 @@ public:
|
||||||
* so that a sharing context could be created.
|
* so that a sharing context could be created.
|
||||||
*/
|
*/
|
||||||
bool supportsSurfacelessContext() const;
|
bool supportsSurfacelessContext() const;
|
||||||
|
/**
|
||||||
|
* Whether our Compositing EGL display supports creating native EGL fences.
|
||||||
|
*/
|
||||||
|
bool supportsNativeFence() const;
|
||||||
/**
|
/**
|
||||||
* The EGLDisplay used by the compositing scene.
|
* The EGLDisplay used by the compositing scene.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -154,6 +154,7 @@ bool AbstractEglBackend::initEglAPI()
|
||||||
const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS);
|
const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS);
|
||||||
setExtensions(eglExtensions.split(' '));
|
setExtensions(eglExtensions.split(' '));
|
||||||
setSupportsSurfacelessContext(hasExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context")));
|
setSupportsSurfacelessContext(hasExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context")));
|
||||||
|
setSupportsNativeFence(hasExtension(QByteArrayLiteral("EGL_ANDROID_native_fence_sync")));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,10 @@ public:
|
||||||
{
|
{
|
||||||
return m_haveSurfacelessContext;
|
return m_haveSurfacelessContext;
|
||||||
}
|
}
|
||||||
|
bool supportsNativeFence() const
|
||||||
|
{
|
||||||
|
return m_haveNativeFence;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the damage that has accumulated since a buffer of the given age was presented.
|
* Returns the damage that has accumulated since a buffer of the given age was presented.
|
||||||
|
@ -270,6 +274,11 @@ protected:
|
||||||
m_haveSurfacelessContext = value;
|
m_haveSurfacelessContext = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSupportsNativeFence(bool value)
|
||||||
|
{
|
||||||
|
m_haveNativeFence = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return const QRegion& Damage of previously rendered frame
|
* @return const QRegion& Damage of previously rendered frame
|
||||||
*/
|
*/
|
||||||
|
@ -323,6 +332,10 @@ private:
|
||||||
* @brief Whether the backend supports EGL_KHR_surfaceless_context.
|
* @brief Whether the backend supports EGL_KHR_surfaceless_context.
|
||||||
*/
|
*/
|
||||||
bool m_haveSurfacelessContext = false;
|
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.
|
* @brief Whether the initialization failed, of course default to @c false.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -865,6 +865,11 @@ bool SceneOpenGL::supportsSurfacelessContext() const
|
||||||
return m_backend->supportsSurfacelessContext();
|
return m_backend->supportsSurfacelessContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SceneOpenGL::supportsNativeFence() const
|
||||||
|
{
|
||||||
|
return m_backend->supportsNativeFence();
|
||||||
|
}
|
||||||
|
|
||||||
Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame)
|
Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame)
|
||||||
{
|
{
|
||||||
return new SceneOpenGL::EffectFrame(frame, this);
|
return new SceneOpenGL::EffectFrame(frame, this);
|
||||||
|
|
|
@ -46,6 +46,7 @@ public:
|
||||||
bool makeOpenGLContextCurrent() override;
|
bool makeOpenGLContextCurrent() override;
|
||||||
void doneOpenGLContextCurrent() override;
|
void doneOpenGLContextCurrent() override;
|
||||||
bool supportsSurfacelessContext() const override;
|
bool supportsSurfacelessContext() const override;
|
||||||
|
bool supportsNativeFence() const override;
|
||||||
Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override;
|
Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override;
|
||||||
void triggerFence() override;
|
void triggerFence() override;
|
||||||
virtual QMatrix4x4 projectionMatrix() const = 0;
|
virtual QMatrix4x4 projectionMatrix() const = 0;
|
||||||
|
|
|
@ -686,6 +686,11 @@ bool Scene::supportsSurfacelessContext() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Scene::supportsNativeFence() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::triggerFence()
|
void Scene::triggerFence()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
1
scene.h
1
scene.h
|
@ -140,6 +140,7 @@ public:
|
||||||
virtual bool makeOpenGLContextCurrent();
|
virtual bool makeOpenGLContextCurrent();
|
||||||
virtual void doneOpenGLContextCurrent();
|
virtual void doneOpenGLContextCurrent();
|
||||||
virtual bool supportsSurfacelessContext() const;
|
virtual bool supportsSurfacelessContext() const;
|
||||||
|
virtual bool supportsNativeFence() const;
|
||||||
|
|
||||||
virtual QMatrix4x4 screenProjectionMatrix() const;
|
virtual QMatrix4x4 screenProjectionMatrix() const;
|
||||||
|
|
||||||
|
|
50
screencast/eglnativefence.cpp
Normal file
50
screencast/eglnativefence.cpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
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
|
32
screencast/eglnativefence.h
Normal file
32
screencast/eglnativefence.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
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 "pipewirestream.h"
|
||||||
#include "cursor.h"
|
#include "cursor.h"
|
||||||
#include "dmabuftexture.h"
|
#include "dmabuftexture.h"
|
||||||
|
#include "eglnativefence.h"
|
||||||
#include "kwingltexture.h"
|
#include "kwingltexture.h"
|
||||||
#include "kwinglutils.h"
|
#include "kwinglutils.h"
|
||||||
#include "kwinscreencast_logging.h"
|
#include "kwinscreencast_logging.h"
|
||||||
|
@ -298,6 +299,11 @@ void PipeWireStream::recordFrame(GLTexture *frameTexture, const QRegion &damaged
|
||||||
Q_ASSERT(!m_stopped);
|
Q_ASSERT(!m_stopped);
|
||||||
Q_ASSERT(frameTexture);
|
Q_ASSERT(frameTexture);
|
||||||
|
|
||||||
|
if (m_pendingBuffer) {
|
||||||
|
qCWarning(KWIN_SCREENCAST) << "Dropping a screencast frame because the compositor is slow";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (frameTexture->size() != m_resolution) {
|
if (frameTexture->size() != m_resolution) {
|
||||||
m_resolution = frameTexture->size();
|
m_resolution = frameTexture->size();
|
||||||
newStreamParams();
|
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)));
|
(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
|
QRect PipeWireStream::cursorGeometry(Cursor *cursor) const
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
#include <spa/param/format-utils.h>
|
#include <spa/param/format-utils.h>
|
||||||
|
@ -28,6 +29,7 @@ namespace KWin
|
||||||
|
|
||||||
class Cursor;
|
class Cursor;
|
||||||
class DmaBufTexture;
|
class DmaBufTexture;
|
||||||
|
class EGLNativeFence;
|
||||||
class GLTexture;
|
class GLTexture;
|
||||||
class PipeWireCore;
|
class PipeWireCore;
|
||||||
|
|
||||||
|
@ -68,6 +70,8 @@ private:
|
||||||
void coreFailed(const QString &errorMessage);
|
void coreFailed(const QString &errorMessage);
|
||||||
void sendCursorData(Cursor *cursor, spa_meta_cursor *spa_cursor);
|
void sendCursorData(Cursor *cursor, spa_meta_cursor *spa_cursor);
|
||||||
void newStreamParams();
|
void newStreamParams();
|
||||||
|
void tryEnqueue(pw_buffer *buffer);
|
||||||
|
void enqueue();
|
||||||
|
|
||||||
QSharedPointer<PipeWireCore> pwCore;
|
QSharedPointer<PipeWireCore> pwCore;
|
||||||
struct pw_stream *pwStream = nullptr;
|
struct pw_stream *pwStream = nullptr;
|
||||||
|
@ -96,6 +100,10 @@ private:
|
||||||
QRect cursorGeometry(Cursor *cursor) const;
|
QRect cursorGeometry(Cursor *cursor) const;
|
||||||
|
|
||||||
QHash<struct pw_buffer *, QSharedPointer<DmaBufTexture>> m_dmabufDataForPwBuffer;
|
QHash<struct pw_buffer *, QSharedPointer<DmaBufTexture>> m_dmabufDataForPwBuffer;
|
||||||
|
|
||||||
|
pw_buffer *m_pendingBuffer = nullptr;
|
||||||
|
QSocketNotifier *m_pendingNotifier = nullptr;
|
||||||
|
EGLNativeFence *m_pendingFence = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace KWin
|
} // namespace KWin
|
||||||
|
|
|
@ -78,7 +78,6 @@ private:
|
||||||
recordFrame(frameTexture.data(), m_damagedRegion);
|
recordFrame(frameTexture.data(), m_damagedRegion);
|
||||||
frameTexture->setYInverted(wasYInverted);
|
frameTexture->setYInverted(wasYInverted);
|
||||||
m_damagedRegion = {};
|
m_damagedRegion = {};
|
||||||
glFinish(); // TODO: Don't stall the whole pipeline. Use EGL_ANDROID_native_fence_sync.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QRegion m_damagedRegion;
|
QRegion m_damagedRegion;
|
||||||
|
|
Loading…
Reference in a new issue