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 }