backends/x11: Use DRI3 directly

At the moment, the buffers for wsi are allocated implicitly by the EGL
implementation, which is fine for "normal" use cases. But we start
hitting the ceiling the moment we need to something more advanced. For
example the EGL backend creates a dummy fbo object wrapping the default
framebuffer, meaning that we cannot pass it to qtquick (because it can
use its own opengl context).

Another reason for using explicit buffers is that it lets us to clean up
some output related abstractions.
This commit is contained in:
Vlad Zahorodnii 2022-12-10 21:40:30 +02:00
parent d34cf36bfa
commit 19fec2a657
10 changed files with 347 additions and 86 deletions

View file

@ -274,6 +274,7 @@ find_package(XCB 1.10 REQUIRED COMPONENTS
COMPOSITE
CURSOR
DAMAGE
DRI3
GLX
ICCCM
IMAGE

View file

@ -9,6 +9,7 @@ target_sources(kwin PRIVATE
target_link_libraries(kwin
X11::XCB
X11::X11
XCB::DRI3
XCB::PRESENT
)
if (X11_Xi_FOUND)

View file

@ -25,6 +25,7 @@
#include <QCoreApplication>
#include <QSocketNotifier>
// xcb
#include <xcb/dri3.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/present.h>
#include <xcb/shm.h>
@ -38,6 +39,9 @@
// system
#include <X11/Xlib-xcb.h>
#include <X11/keysym.h>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <gbm.h>
#include <linux/input.h>
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<xcb_dri3_query_version_reply_t> 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<xcb_dri3_open_reply_t> 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<uint64_t> &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<xcb_dri3_get_supported_modifiers_reply_t> 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<uint32_t, QVector<uint64_t>> 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<CompositingType> X11WindowedBackend::supportedCompositors() const
{
QVector<CompositingType> ret{OpenGLCompositing};
QVector<CompositingType> ret;
if (m_gbmDevice) {
ret.append(OpenGLCompositing);
}
if (m_hasShm) {
ret.append(QPainterCompositing);
}

View file

@ -11,6 +11,7 @@
#include "core/inputbackend.h"
#include "core/inputdevice.h"
#include "core/outputbackend.h"
#include "utils/filedescriptor.h"
#include <kwin_export.h>
@ -19,6 +20,7 @@
#include <xcb/xcb.h>
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<uint32_t, QVector<uint64_t>> driFormats() const;
uint32_t driFormatForDepth(int depth) const;
int driMajorVersion() const;
int driMinorVersion() const;
bool initialize() override;
std::unique_ptr<OpenGLBackend> createOpenGLBackend() override;
std::unique_ptr<QPainterBackend> 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<uint32_t, QVector<uint64_t>> m_driFormats;
FileDescriptor m_drmFileDescriptor;
gbm_device *m_gbmDevice = nullptr;
QVector<X11WindowedOutput *> m_outputs;
};

View file

@ -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 <drm_fourcc.h>
#include <kwinglplatform.h>
#include <gbm.h>
#include <xcb/dri3.h>
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<uint64_t> &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<GLFramebuffer>(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<GLTexture> 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<uint64_t> &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<X11WindowedEglLayerBuffer>(size, format, depth, bpp, modifiers, drawable, backend));
}
}
X11WindowedEglLayerSwapchain::~X11WindowedEglLayerSwapchain()
{
}
QSize X11WindowedEglLayerSwapchain::size() const
{
return m_size;
}
std::shared_ptr<X11WindowedEglLayerBuffer> X11WindowedEglLayerSwapchain::acquire()
{
m_index = (m_index + 1) % m_buffers.count();
return m_buffers[m_index];
}
void X11WindowedEglLayerSwapchain::release(std::shared_ptr<X11WindowedEglLayerBuffer> 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<GLFramebuffer>(0, m_output->pixelSize());
}
}
std::optional<OutputLayerBeginFrameInfo> 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<uint32_t, QVector<uint64_t>> formatTable = m_backend->backend()->driFormats();
if (!formatTable.contains(format)) {
return std::nullopt;
}
m_swapchain = std::make_unique<X11WindowedEglLayerSwapchain>(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<GLTexture> 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<X11WindowedOutput *>(output);
EGLSurface s = createSurface(x11Output->window());
if (s == EGL_NO_SURFACE) {
return false;
}
m_outputs[output] = Layers{
.primaryLayer = std::make_unique<X11WindowedEglPrimaryLayer>(this, x11Output, s),
.primaryLayer = std::make_unique<X11WindowedEglPrimaryLayer>(this, x11Output),
.cursorLayer = std::make_unique<X11WindowedEglCursorLayer>(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<GLTexture> 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

View file

@ -14,6 +14,8 @@
#include <QMap>
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<uint64_t> &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend);
~X11WindowedEglLayerBuffer();
xcb_pixmap_t pixmap() const;
std::shared_ptr<GLTexture> 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<GLFramebuffer> m_framebuffer;
std::shared_ptr<GLTexture> 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<uint64_t> &modifiers, xcb_drawable_t drawable, X11WindowedEglBackend *backend);
~X11WindowedEglLayerSwapchain();
QSize size() const;
std::shared_ptr<X11WindowedEglLayerBuffer> acquire();
void release(std::shared_ptr<X11WindowedEglLayerBuffer> buffer);
private:
X11WindowedEglBackend *m_backend;
QSize m_size;
QVector<std::shared_ptr<X11WindowedEglLayerBuffer>> 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<OutputLayerBeginFrameInfo> 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<GLTexture> texture() const;
void present();
private:
void ensureFbo();
EGLSurface m_eglSurface;
std::unique_ptr<GLFramebuffer> m_fbo;
QRegion m_lastDamage;
std::unique_ptr<X11WindowedEglLayerSwapchain> m_swapchain;
std::shared_ptr<X11WindowedEglLayerBuffer> 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<SurfaceTexture> createSurfaceTextureInternal(SurfacePixmapInternal *pixmap) override;
std::unique_ptr<SurfaceTexture> createSurfaceTextureWayland(SurfacePixmapWayland *pixmap) override;
std::shared_ptr<GLTexture> 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<X11WindowedEglPrimaryLayer> primaryLayer;

View file

@ -367,14 +367,6 @@ void AbstractEglBackend::setSurface(const EGLSurface &surface)
m_surface = surface;
}
std::shared_ptr<GLTexture> AbstractEglBackend::textureForOutput(Output *requestedOutput) const
{
std::shared_ptr<GLTexture> 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;

View file

@ -62,7 +62,6 @@ public:
return m_config;
}
std::shared_ptr<GLTexture> textureForOutput(Output *output) const override;
QHash<uint32_t, QVector<uint64_t>> supportedFormats() const override;
dev_t deviceId() const;

View file

@ -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) {

View file

@ -24,6 +24,7 @@ public:
bool isValid() const;
int get() const;
int take();
FileDescriptor duplicate() const;
private: