File indexing completed on 2024-04-21 16:13:06
0001 // SPDX-FileCopyrightText: 2022 Aleix Pol i Gonzalez <aleixpol@kde.org> 0002 // SPDX-License-Identifier: Apache-2.0 0003 0004 #include "dmabufhandler.h" 0005 #include "glhelpers.h" 0006 #include <QGuiApplication> 0007 #include <fcntl.h> 0008 #include <gbm.h> 0009 #include <logging_dmabuf.h> 0010 #include <qpa/qplatformnativeinterface.h> 0011 #include <unistd.h> 0012 #include <xf86drm.h> 0013 0014 static QByteArray fetchRenderNode() 0015 { 0016 int max_devices = drmGetDevices2(0, nullptr, 0); 0017 if (max_devices <= 0) { 0018 qCWarning(PIPEWIREDMABUF_LOGGING) << "drmGetDevices2() has not found any devices (errno=" << -max_devices << ")"; 0019 return "/dev/dri/renderD128"; 0020 } 0021 0022 std::vector<drmDevicePtr> devices(max_devices); 0023 int ret = drmGetDevices2(0, devices.data(), max_devices); 0024 if (ret < 0) { 0025 qCWarning(PIPEWIREDMABUF_LOGGING) << "drmGetDevices2() returned an error " << ret; 0026 return "/dev/dri/renderD128"; 0027 } 0028 0029 QByteArray render_node; 0030 0031 for (const drmDevicePtr &device : devices) { 0032 if (device->available_nodes & (1 << DRM_NODE_RENDER)) { 0033 render_node = device->nodes[DRM_NODE_RENDER]; 0034 break; 0035 } 0036 } 0037 0038 drmFreeDevices(devices.data(), ret); 0039 return render_node; 0040 } 0041 0042 DmaBufHandler::DmaBufHandler() 0043 { 0044 } 0045 0046 DmaBufHandler::~DmaBufHandler() 0047 { 0048 if (m_drmFd) { 0049 close(m_drmFd); 0050 } 0051 } 0052 0053 void DmaBufHandler::setupEgl() 0054 { 0055 if (m_eglInitialized) { 0056 return; 0057 } 0058 0059 m_egl.display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay")); 0060 0061 // Use eglGetPlatformDisplayEXT() to get the display pointer 0062 // if the implementation supports it. 0063 if (!epoxy_has_egl_extension(m_egl.display, "EGL_EXT_platform_base") || !epoxy_has_egl_extension(m_egl.display, "EGL_MESA_platform_gbm")) { 0064 qCWarning(PIPEWIREDMABUF_LOGGING) << "One of required EGL extensions is missing"; 0065 return; 0066 } 0067 0068 if (m_egl.display == EGL_NO_DISPLAY) { 0069 m_egl.display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, (void *)EGL_DEFAULT_DISPLAY, nullptr); 0070 } 0071 if (m_egl.display == EGL_NO_DISPLAY) { 0072 const QByteArray renderNode = fetchRenderNode(); 0073 m_drmFd = open(renderNode.constData(), O_RDWR); 0074 0075 if (m_drmFd < 0) { 0076 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to open drm render node" << renderNode << "with error: " << strerror(errno); 0077 return; 0078 } 0079 0080 m_gbmDevice = gbm_create_device(m_drmFd); 0081 0082 if (!m_gbmDevice) { 0083 qCWarning(PIPEWIREDMABUF_LOGGING) << "Cannot create GBM device: " << strerror(errno); 0084 return; 0085 } 0086 m_egl.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_gbmDevice, nullptr); 0087 } 0088 0089 if (m_egl.display == EGL_NO_DISPLAY) { 0090 qCWarning(PIPEWIREDMABUF_LOGGING) << "Error during obtaining EGL display: " << GLHelpers::formatGLError(eglGetError()); 0091 return; 0092 } 0093 0094 EGLint major, minor; 0095 if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) { 0096 qCWarning(PIPEWIREDMABUF_LOGGING) << "Error during eglInitialize: " << GLHelpers::formatGLError(eglGetError()); 0097 return; 0098 } 0099 0100 if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { 0101 qCWarning(PIPEWIREDMABUF_LOGGING) << "bind OpenGL API failed"; 0102 return; 0103 } 0104 0105 EGLConfig configs; 0106 auto createConfig = [&] { 0107 static const EGLint configAttribs[] = { 0108 EGL_SURFACE_TYPE, 0109 EGL_WINDOW_BIT, 0110 EGL_RED_SIZE, 0111 8, 0112 EGL_GREEN_SIZE, 0113 8, 0114 EGL_BLUE_SIZE, 0115 8, 0116 EGL_RENDERABLE_TYPE, 0117 EGL_OPENGL_BIT, 0118 EGL_CONFIG_CAVEAT, 0119 EGL_NONE, 0120 EGL_NONE, 0121 }; 0122 0123 EGLint count = 333; 0124 if (eglChooseConfig(m_egl.display, configAttribs, &configs, 1, &count) == EGL_FALSE) { 0125 qCWarning(PIPEWIREDMABUF_LOGGING) << "choose config failed"; 0126 return false; 0127 } 0128 // if (count != 1) { 0129 qCWarning(PIPEWIREDMABUF_LOGGING) << "eglChooseConfig returned this many configs:" << count; 0130 // return false; 0131 // } 0132 return true; 0133 }; 0134 0135 bool b = createConfig(); 0136 static const EGLint configAttribs[] = {EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, EGL_NONE}; 0137 Q_ASSERT(configs); 0138 m_egl.context = eglCreateContext(m_egl.display, b ? configs : EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, configAttribs); 0139 0140 Q_ASSERT(b); 0141 Q_ASSERT(m_egl.context); 0142 if (m_egl.context == EGL_NO_CONTEXT) { 0143 qCWarning(PIPEWIREDMABUF_LOGGING) << "Couldn't create EGL context: " << GLHelpers::formatEGLError(eglGetError()); 0144 return; 0145 } 0146 0147 qCDebug(PIPEWIREDMABUF_LOGGING) << "Egl initialization succeeded"; 0148 qCDebug(PIPEWIREDMABUF_LOGGING) << QStringLiteral("EGL version: %1.%2").arg(major).arg(minor); 0149 0150 m_eglInitialized = true; 0151 } 0152 0153 GLenum closestGLType(const QImage &image) 0154 { 0155 switch (image.format()) { 0156 case QImage::Format_RGB888: 0157 return GL_RGB; 0158 case QImage::Format_BGR888: 0159 return GL_BGR; 0160 case QImage::Format_RGB32: 0161 case QImage::Format_RGBX8888: 0162 case QImage::Format_RGBA8888: 0163 case QImage::Format_RGBA8888_Premultiplied: 0164 return GL_RGBA; 0165 default: 0166 qDebug() << "unknown format" << image.format(); 0167 return GL_RGBA; 0168 } 0169 } 0170 0171 bool DmaBufHandler::downloadFrame(QImage &qimage, const PipeWireFrame &frame) 0172 { 0173 Q_ASSERT(frame.dmabuf); 0174 const QSize streamSize = {frame.dmabuf->width, frame.dmabuf->height}; 0175 Q_ASSERT(qimage.size() == streamSize); 0176 setupEgl(); 0177 if (!m_eglInitialized) { 0178 return false; 0179 } 0180 0181 if (!eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context)) { 0182 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to make context current" << GLHelpers::formatEGLError(eglGetError()); 0183 return false; 0184 } 0185 EGLImageKHR image = 0186 GLHelpers::createImage(m_egl.display, *frame.dmabuf, PipeWireSourceStream::spaVideoFormatToDrmFormat(frame.format), qimage.size(), m_gbmDevice); 0187 0188 if (image == EGL_NO_IMAGE_KHR) { 0189 qCWarning(PIPEWIREDMABUF_LOGGING) << "Failed to record frame: Error creating EGLImageKHR - " << GLHelpers::formatEGLError(eglGetError()); 0190 return false; 0191 } 0192 0193 GLHelpers::initDebugOutput(); 0194 // create GL 2D texture for framebuffer 0195 GLuint texture; 0196 GLuint fbo; 0197 glGenTextures(1, &texture); 0198 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 0199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 0200 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 0201 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 0202 glBindTexture(GL_TEXTURE_2D, texture); 0203 0204 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); 0205 glGenFramebuffers(1, &fbo); 0206 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 0207 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0208 texture, 0); 0209 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 0210 glDeleteFramebuffers(1, &fbo); 0211 glDeleteTextures(1, &texture); 0212 eglDestroyImageKHR(m_egl.display, image); 0213 return false; 0214 } 0215 0216 glReadPixels(0, 0, frame.dmabuf->width, frame.dmabuf->height, closestGLType(qimage), GL_UNSIGNED_BYTE, qimage.bits()); 0217 0218 glDeleteFramebuffers(1, &fbo); 0219 glDeleteTextures(1, &texture); 0220 eglDestroyImageKHR(m_egl.display, image); 0221 return true; 0222 }