File indexing completed on 2024-05-12 05:31:41

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "egldisplay.h"
0010 #include "core/graphicsbuffer.h"
0011 #include "opengl/eglutils_p.h"
0012 #include "opengl/glutils.h"
0013 #include "utils/common.h"
0014 
0015 #include <QOpenGLContext>
0016 #include <drm_fourcc.h>
0017 #include <utils/drm_format_helper.h>
0018 
0019 #ifndef EGL_DRM_RENDER_NODE_FILE_EXT
0020 #define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377
0021 #endif
0022 
0023 namespace KWin
0024 {
0025 
0026 static bool isOpenGLES()
0027 {
0028     if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
0029         return true;
0030     }
0031     return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
0032 }
0033 
0034 std::unique_ptr<EglDisplay> EglDisplay::create(::EGLDisplay display, bool owning)
0035 {
0036     if (!display) {
0037         return nullptr;
0038     }
0039     EGLint major, minor;
0040     if (eglInitialize(display, &major, &minor) == EGL_FALSE) {
0041         qCWarning(KWIN_OPENGL) << "eglInitialize failed";
0042         EGLint error = eglGetError();
0043         if (error != EGL_SUCCESS) {
0044             qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error;
0045         }
0046         return nullptr;
0047     }
0048     EGLint error = eglGetError();
0049     if (error != EGL_SUCCESS) {
0050         qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error;
0051         return nullptr;
0052     }
0053     qCDebug(KWIN_OPENGL) << "Egl Initialize succeeded";
0054     if (eglBindAPI(isOpenGLES() ? EGL_OPENGL_ES_API : EGL_OPENGL_API) == EGL_FALSE) {
0055         qCCritical(KWIN_OPENGL) << "bind OpenGL API failed";
0056         return nullptr;
0057     }
0058     qCDebug(KWIN_OPENGL) << "EGL version: " << major << "." << minor;
0059 
0060     const auto extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ');
0061 
0062     const QByteArray requiredExtensions[] = {
0063         QByteArrayLiteral("EGL_KHR_no_config_context"),
0064         QByteArrayLiteral("EGL_KHR_surfaceless_context"),
0065     };
0066     for (const QByteArray &extensionName : requiredExtensions) {
0067         if (!extensions.contains(extensionName)) {
0068             qCWarning(KWIN_OPENGL) << extensionName << "extension is unsupported";
0069             return nullptr;
0070         }
0071     }
0072 
0073     return std::make_unique<EglDisplay>(display, extensions, owning);
0074 }
0075 
0076 EglDisplay::EglDisplay(::EGLDisplay display, const QList<QByteArray> &extensions, bool owning)
0077     : m_handle(display)
0078     , m_extensions(extensions)
0079     , m_owning(owning)
0080     , m_supportsBufferAge(extensions.contains(QByteArrayLiteral("EGL_EXT_buffer_age")) && qgetenv("KWIN_USE_BUFFER_AGE") != "0")
0081     , m_supportsNativeFence(extensions.contains(QByteArrayLiteral("EGL_ANDROID_native_fence_sync")))
0082     , m_importFormats(queryImportFormats())
0083 {
0084 }
0085 
0086 EglDisplay::~EglDisplay()
0087 {
0088     if (m_owning) {
0089         eglTerminate(m_handle);
0090     }
0091 }
0092 
0093 QList<QByteArray> EglDisplay::extensions() const
0094 {
0095     return m_extensions;
0096 }
0097 
0098 ::EGLDisplay EglDisplay::handle() const
0099 {
0100     return m_handle;
0101 }
0102 
0103 bool EglDisplay::hasExtension(const QByteArray &name) const
0104 {
0105     return m_extensions.contains(name);
0106 }
0107 
0108 static bool checkExtension(const QByteArrayView extensions, const QByteArrayView extension)
0109 {
0110     for (int i = 0; i < extensions.size();) {
0111         if (extensions[i] == ' ') {
0112             i++;
0113             continue;
0114         }
0115         int next = extensions.indexOf(' ', i);
0116         if (next == -1) {
0117             next = extensions.size();
0118         }
0119 
0120         const int size = next - i;
0121         if (extension.size() == size && extensions.sliced(i, size) == extension) {
0122             return true;
0123         }
0124 
0125         i = next;
0126     }
0127 
0128     return false;
0129 }
0130 
0131 QString EglDisplay::renderNode() const
0132 {
0133     const char *clientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
0134     if (checkExtension(clientExtensions, "EGL_EXT_device_query")) {
0135         EGLAttrib eglDeviceAttrib;
0136         if (eglQueryDisplayAttribEXT(m_handle, EGL_DEVICE_EXT, &eglDeviceAttrib)) {
0137             EGLDeviceEXT eglDevice = reinterpret_cast<EGLDeviceEXT>(eglDeviceAttrib);
0138 
0139             const char *deviceExtensions = eglQueryDeviceStringEXT(eglDevice, EGL_EXTENSIONS);
0140             if (checkExtension(deviceExtensions, "EGL_EXT_device_drm_render_node")) {
0141                 if (const char *node = eglQueryDeviceStringEXT(eglDevice, EGL_DRM_RENDER_NODE_FILE_EXT)) {
0142                     return QString::fromLocal8Bit(node);
0143                 }
0144             }
0145             if (checkExtension(deviceExtensions, "EGL_EXT_device_drm")) {
0146                 // Fallback to display device.
0147                 if (const char *node = eglQueryDeviceStringEXT(eglDevice, EGL_DRM_DEVICE_FILE_EXT)) {
0148                     return QString::fromLocal8Bit(node);
0149                 }
0150             }
0151         }
0152     }
0153     return QString();
0154 }
0155 
0156 bool EglDisplay::supportsBufferAge() const
0157 {
0158     return m_supportsBufferAge;
0159 }
0160 
0161 bool EglDisplay::supportsNativeFence() const
0162 {
0163     return m_supportsNativeFence;
0164 }
0165 
0166 EGLImageKHR EglDisplay::importDmaBufAsImage(const DmaBufAttributes &dmabuf) const
0167 {
0168     QList<EGLint> attribs;
0169     attribs.reserve(6 + dmabuf.planeCount * 10 + 1);
0170 
0171     attribs << EGL_WIDTH << dmabuf.width
0172             << EGL_HEIGHT << dmabuf.height
0173             << EGL_LINUX_DRM_FOURCC_EXT << dmabuf.format;
0174 
0175     attribs << EGL_DMA_BUF_PLANE0_FD_EXT << dmabuf.fd[0].get()
0176             << EGL_DMA_BUF_PLANE0_OFFSET_EXT << dmabuf.offset[0]
0177             << EGL_DMA_BUF_PLANE0_PITCH_EXT << dmabuf.pitch[0];
0178     if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
0179         attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
0180                 << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
0181     }
0182 
0183     if (dmabuf.planeCount > 1) {
0184         attribs << EGL_DMA_BUF_PLANE1_FD_EXT << dmabuf.fd[1].get()
0185                 << EGL_DMA_BUF_PLANE1_OFFSET_EXT << dmabuf.offset[1]
0186                 << EGL_DMA_BUF_PLANE1_PITCH_EXT << dmabuf.pitch[1];
0187         if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
0188             attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
0189                     << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
0190         }
0191     }
0192 
0193     if (dmabuf.planeCount > 2) {
0194         attribs << EGL_DMA_BUF_PLANE2_FD_EXT << dmabuf.fd[2].get()
0195                 << EGL_DMA_BUF_PLANE2_OFFSET_EXT << dmabuf.offset[2]
0196                 << EGL_DMA_BUF_PLANE2_PITCH_EXT << dmabuf.pitch[2];
0197         if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
0198             attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
0199                     << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
0200         }
0201     }
0202 
0203     if (dmabuf.planeCount > 3) {
0204         attribs << EGL_DMA_BUF_PLANE3_FD_EXT << dmabuf.fd[3].get()
0205                 << EGL_DMA_BUF_PLANE3_OFFSET_EXT << dmabuf.offset[3]
0206                 << EGL_DMA_BUF_PLANE3_PITCH_EXT << dmabuf.pitch[3];
0207         if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
0208             attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
0209                     << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
0210         }
0211     }
0212 
0213     attribs << EGL_NONE;
0214 
0215     return eglCreateImageKHR(m_handle, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
0216 }
0217 
0218 EGLImageKHR EglDisplay::importDmaBufAsImage(const DmaBufAttributes &dmabuf, int plane, int format, const QSize &size) const
0219 {
0220     QList<EGLint> attribs;
0221     attribs.reserve(6 + 1 * 10 + 1);
0222 
0223     attribs << EGL_WIDTH << size.width()
0224             << EGL_HEIGHT << size.height()
0225             << EGL_LINUX_DRM_FOURCC_EXT << format;
0226 
0227     attribs << EGL_DMA_BUF_PLANE0_FD_EXT << dmabuf.fd[plane].get()
0228             << EGL_DMA_BUF_PLANE0_OFFSET_EXT << dmabuf.offset[plane]
0229             << EGL_DMA_BUF_PLANE0_PITCH_EXT << dmabuf.pitch[plane];
0230     if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
0231         attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
0232                 << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
0233     }
0234     attribs << EGL_NONE;
0235 
0236     return eglCreateImageKHR(m_handle, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
0237 }
0238 
0239 QHash<uint32_t, EglDisplay::DrmFormatInfo> EglDisplay::allSupportedDrmFormats() const
0240 {
0241     return m_importFormats;
0242 }
0243 
0244 QHash<uint32_t, QList<uint64_t>> EglDisplay::nonExternalOnlySupportedDrmFormats() const
0245 {
0246     QHash<uint32_t, QList<uint64_t>> ret;
0247     ret.reserve(m_importFormats.size());
0248     for (auto it = m_importFormats.constBegin(), itEnd = m_importFormats.constEnd(); it != itEnd; ++it) {
0249         ret[it.key()] = it->nonExternalOnlyModifiers;
0250     }
0251     return ret;
0252 }
0253 
0254 bool EglDisplay::isExternalOnly(uint32_t format, uint64_t modifier) const
0255 {
0256     if (const auto it = m_importFormats.find(format); it != m_importFormats.end()) {
0257         return it->externalOnlyModifiers.contains(modifier);
0258     } else {
0259         return false;
0260     }
0261 }
0262 
0263 QHash<uint32_t, EglDisplay::DrmFormatInfo> EglDisplay::queryImportFormats() const
0264 {
0265     if (!hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import")) || !hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) {
0266         return {};
0267     }
0268 
0269     typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats);
0270     typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers);
0271     eglQueryDmaBufFormatsEXT_func eglQueryDmaBufFormatsEXT = nullptr;
0272     eglQueryDmaBufModifiersEXT_func eglQueryDmaBufModifiersEXT = nullptr;
0273     eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)eglGetProcAddress("eglQueryDmaBufFormatsEXT");
0274     eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXT_func)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
0275     if (eglQueryDmaBufFormatsEXT == nullptr) {
0276         return {};
0277     }
0278 
0279     EGLint count = 0;
0280     EGLBoolean success = eglQueryDmaBufFormatsEXT(m_handle, 0, nullptr, &count);
0281     if (!success || count == 0) {
0282         qCCritical(KWIN_OPENGL) << "eglQueryDmaBufFormatsEXT failed!" << getEglErrorString();
0283         return {};
0284     }
0285     QList<uint32_t> formats(count);
0286     if (!eglQueryDmaBufFormatsEXT(m_handle, count, (EGLint *)formats.data(), &count)) {
0287         qCCritical(KWIN_OPENGL) << "eglQueryDmaBufFormatsEXT with count" << count << "failed!" << getEglErrorString();
0288         return {};
0289     }
0290     QHash<uint32_t, DrmFormatInfo> ret;
0291     for (const auto format : std::as_const(formats)) {
0292         if (eglQueryDmaBufModifiersEXT != nullptr) {
0293             EGLint count = 0;
0294             const EGLBoolean success = eglQueryDmaBufModifiersEXT(m_handle, format, 0, nullptr, nullptr, &count);
0295             if (success && count > 0) {
0296                 DrmFormatInfo drmFormatInfo;
0297                 drmFormatInfo.allModifiers.resize(count);
0298                 QList<EGLBoolean> externalOnly(count);
0299                 if (eglQueryDmaBufModifiersEXT(m_handle, format, count, drmFormatInfo.allModifiers.data(), externalOnly.data(), &count)) {
0300                     drmFormatInfo.externalOnlyModifiers = drmFormatInfo.allModifiers;
0301                     drmFormatInfo.nonExternalOnlyModifiers = drmFormatInfo.allModifiers;
0302                     for (int i = drmFormatInfo.allModifiers.size() - 1; i >= 0; i--) {
0303                         if (externalOnly[i]) {
0304                             drmFormatInfo.nonExternalOnlyModifiers.removeAll(drmFormatInfo.allModifiers[i]);
0305                         } else {
0306                             drmFormatInfo.externalOnlyModifiers.removeAll(drmFormatInfo.allModifiers[i]);
0307                         }
0308                     }
0309                     if (!drmFormatInfo.allModifiers.empty()) {
0310                         if (!drmFormatInfo.allModifiers.contains(DRM_FORMAT_MOD_INVALID)) {
0311                             drmFormatInfo.allModifiers.push_back(DRM_FORMAT_MOD_INVALID);
0312                             drmFormatInfo.nonExternalOnlyModifiers.push_back(DRM_FORMAT_MOD_INVALID);
0313                         }
0314                         ret.insert(format, drmFormatInfo);
0315                     }
0316                     continue;
0317                 }
0318             }
0319         }
0320         DrmFormatInfo drmFormat;
0321         drmFormat.allModifiers = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
0322         drmFormat.nonExternalOnlyModifiers = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
0323         ret.insert(format, drmFormat);
0324     }
0325     return ret;
0326 }
0327 
0328 }