KPipewire

dmabufhandler.cpp
1// SPDX-FileCopyrightText: 2022 Aleix Pol i Gonzalez <aleixpol@kde.org>
2// SPDX-License-Identifier: Apache-2.0
3
4#include "dmabufhandler.h"
5#include "glhelpers.h"
6#include <QGuiApplication>
7#include <fcntl.h>
8#include <gbm.h>
9#include <logging_dmabuf.h>
10#include <qpa/qplatformnativeinterface.h>
11#include <unistd.h>
12#include <xf86drm.h>
13
14static QByteArray fetchRenderNode()
15{
16 int max_devices = drmGetDevices2(0, nullptr, 0);
17 if (max_devices <= 0) {
18 qCWarning(PIPEWIREDMABUF_LOGGING) << "drmGetDevices2() has not found any devices (errno=" << -max_devices << ")";
19 return "/dev/dri/renderD128";
20 }
21
22 std::vector<drmDevicePtr> devices(max_devices);
23 int ret = drmGetDevices2(0, devices.data(), max_devices);
24 if (ret < 0) {
25 qCWarning(PIPEWIREDMABUF_LOGGING) << "drmGetDevices2() returned an error " << ret;
26 return "/dev/dri/renderD128";
27 }
28
29 QByteArray render_node;
30
31 for (const drmDevicePtr &device : devices) {
32 if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
33 render_node = device->nodes[DRM_NODE_RENDER];
34 break;
35 }
36 }
37
38 drmFreeDevices(devices.data(), ret);
39 return render_node;
40}
41
42DmaBufHandler::DmaBufHandler()
43{
44}
45
46DmaBufHandler::~DmaBufHandler()
47{
48 if (m_drmFd) {
49 close(m_drmFd);
50 }
51}
52
53void DmaBufHandler::setupEgl()
54{
55 if (m_eglInitialized) {
56 return;
57 }
58
59 m_egl.display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
60
61 // Use eglGetPlatformDisplayEXT() to get the display pointer
62 // if the implementation supports it.
63 if (!epoxy_has_egl_extension(m_egl.display, "EGL_EXT_platform_base") || !epoxy_has_egl_extension(m_egl.display, "EGL_MESA_platform_gbm")) {
64 qCWarning(PIPEWIREDMABUF_LOGGING) << "One of required EGL extensions is missing";
65 return;
66 }
67
68 if (m_egl.display == EGL_NO_DISPLAY) {
69 m_egl.display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, (void *)EGL_DEFAULT_DISPLAY, nullptr);
70 }
71 if (m_egl.display == EGL_NO_DISPLAY) {
72 const QByteArray renderNode = fetchRenderNode();
73 m_drmFd = open(renderNode.constData(), O_RDWR);
74
75 if (m_drmFd < 0) {
76 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to open drm render node" << renderNode << "with error: " << strerror(errno);
77 return;
78 }
79
80 m_gbmDevice = gbm_create_device(m_drmFd);
81
82 if (!m_gbmDevice) {
83 qCWarning(PIPEWIREDMABUF_LOGGING) << "Cannot create GBM device: " << strerror(errno);
84 return;
85 }
86 m_egl.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_gbmDevice, nullptr);
87 }
88
89 if (m_egl.display == EGL_NO_DISPLAY) {
90 qCWarning(PIPEWIREDMABUF_LOGGING) << "Error during obtaining EGL display: " << GLHelpers::formatGLError(eglGetError());
91 return;
92 }
93
94 EGLint major, minor;
95 if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) {
96 qCWarning(PIPEWIREDMABUF_LOGGING) << "Error during eglInitialize: " << GLHelpers::formatGLError(eglGetError());
97 return;
98 }
99
100 if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
101 qCWarning(PIPEWIREDMABUF_LOGGING) << "bind OpenGL API failed";
102 return;
103 }
104
105 EGLConfig configs;
106 auto createConfig = [&] {
107 static const EGLint configAttribs[] = {
108 EGL_SURFACE_TYPE,
109 EGL_WINDOW_BIT,
110 EGL_RED_SIZE,
111 8,
112 EGL_GREEN_SIZE,
113 8,
114 EGL_BLUE_SIZE,
115 8,
116 EGL_RENDERABLE_TYPE,
117 EGL_OPENGL_BIT,
118 EGL_CONFIG_CAVEAT,
119 EGL_NONE,
120 EGL_NONE,
121 };
122
123 EGLint count = 333;
124 if (eglChooseConfig(m_egl.display, configAttribs, &configs, 1, &count) == EGL_FALSE) {
125 qCWarning(PIPEWIREDMABUF_LOGGING) << "choose config failed";
126 return false;
127 }
128 // if (count != 1) {
129 qCWarning(PIPEWIREDMABUF_LOGGING) << "eglChooseConfig returned this many configs:" << count;
130 // return false;
131 // }
132 return true;
133 };
134
135 bool b = createConfig();
136 static const EGLint configAttribs[] = {EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, EGL_NONE};
137 Q_ASSERT(configs);
138 m_egl.context = eglCreateContext(m_egl.display, b ? configs : EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, configAttribs);
139
140 Q_ASSERT(b);
141 Q_ASSERT(m_egl.context);
142 if (m_egl.context == EGL_NO_CONTEXT) {
143 qCWarning(PIPEWIREDMABUF_LOGGING) << "Couldn't create EGL context: " << GLHelpers::formatEGLError(eglGetError());
144 return;
145 }
146
147 qCDebug(PIPEWIREDMABUF_LOGGING) << "Egl initialization succeeded";
148 qCDebug(PIPEWIREDMABUF_LOGGING) << QStringLiteral("EGL version: %1.%2").arg(major).arg(minor);
149
150 m_eglInitialized = true;
151}
152
153GLenum closestGLType(const QImage &image)
154{
155 switch (image.format()) {
157 return GL_RGB;
159 return GL_BGR;
164 return GL_RGBA;
165 default:
166 qDebug() << "cannot convert QImage format to GLType" << image.format();
167 return GL_RGBA;
168 }
169}
170
171bool DmaBufHandler::downloadFrame(QImage &qimage, const PipeWireFrame &frame)
172{
173 Q_ASSERT(frame.dmabuf);
174 const QSize streamSize = {frame.dmabuf->width, frame.dmabuf->height};
175 Q_ASSERT(qimage.size() == streamSize);
176 setupEgl();
177 if (!m_eglInitialized) {
178 return false;
179 }
180
181 if (!eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context)) {
182 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to make context current" << GLHelpers::formatEGLError(eglGetError());
183 return false;
184 }
185 EGLImageKHR image =
186 GLHelpers::createImage(m_egl.display, *frame.dmabuf, PipeWireSourceStream::spaVideoFormatToDrmFormat(frame.format), qimage.size(), m_gbmDevice);
187
188 if (image == EGL_NO_IMAGE_KHR) {
189 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to record frame: Error creating EGLImageKHR - " << GLHelpers::formatEGLError(eglGetError());
190 return false;
191 }
192
193 GLHelpers::initDebugOutput();
194 // create GL 2D texture for framebuffer
195 GLuint texture;
196 GLuint fbo;
197 glGenTextures(1, &texture);
198 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
200 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
201 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
202 glBindTexture(GL_TEXTURE_2D, texture);
203
204 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
205 glGenFramebuffers(1, &fbo);
206 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
207 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
208 texture, 0);
209 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
210 glDeleteFramebuffers(1, &fbo);
211 glDeleteTextures(1, &texture);
212 eglDestroyImageKHR(m_egl.display, image);
213 return false;
214 }
215
216 glReadPixels(0, 0, frame.dmabuf->width, frame.dmabuf->height, closestGLType(qimage), GL_UNSIGNED_BYTE, qimage.bits());
217
218 glDeleteFramebuffers(1, &fbo);
219 glDeleteTextures(1, &texture);
220 eglDestroyImageKHR(m_egl.display, image);
221 return true;
222}
KGuiItem open()
KGuiItem close()
const char * constData() const const
uchar * bits()
Format format() const const
QSize size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:08:09 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.