/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright © 2019 Roman Gilg Copyright © 2018 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "linux_dmabuf.h" #include "drm_fourcc.h" #include "../../../wayland_server.h" #include #include #include namespace KWin { typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); eglQueryDmaBufFormatsEXT_func eglQueryDmaBufFormatsEXT = nullptr; eglQueryDmaBufModifiersEXT_func eglQueryDmaBufModifiersEXT = nullptr; #ifndef EGL_EXT_image_dma_buf_import #define EGL_LINUX_DMA_BUF_EXT 0x3270 #define EGL_LINUX_DRM_FOURCC_EXT 0x3271 #define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 #define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 #define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 #define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 #define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 #define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 #define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 #define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 #define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A #define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B #define EGL_SAMPLE_RANGE_HINT_EXT 0x327C #define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D #define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E #define EGL_ITU_REC601_EXT 0x327F #define EGL_ITU_REC709_EXT 0x3280 #define EGL_ITU_REC2020_EXT 0x3281 #define EGL_YUV_FULL_RANGE_EXT 0x3282 #define EGL_YUV_NARROW_RANGE_EXT 0x3283 #define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 #define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 #endif // EGL_EXT_image_dma_buf_import #ifndef EGL_EXT_image_dma_buf_import_modifiers #define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 #define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 #define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 #define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 #define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 #define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 #define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 #define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 #define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 #define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A #endif // EGL_EXT_image_dma_buf_import_modifiers struct YuvPlane { int widthDivisor; int heightDivisor; uint32_t format; int planeIndex; }; struct YuvFormat { uint32_t format; int inputPlanes; int outputPlanes; int textureType; struct YuvPlane planes[3]; }; YuvFormat yuvFormats[] = { { DRM_FORMAT_YUYV, 1, 2, EGL_TEXTURE_Y_XUXV_WL, { { 1, 1, DRM_FORMAT_GR88, 0 }, { 2, 1, DRM_FORMAT_ARGB8888, 0 } } }, { DRM_FORMAT_NV12, 2, 2, EGL_TEXTURE_Y_UV_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 2, 2, DRM_FORMAT_GR88, 1 } } }, { DRM_FORMAT_YUV420, 3, 3, EGL_TEXTURE_Y_U_V_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 2, 2, DRM_FORMAT_R8, 1 }, { 2, 2, DRM_FORMAT_R8, 2 } } }, { DRM_FORMAT_YUV444, 3, 3, EGL_TEXTURE_Y_U_V_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 1, 1, DRM_FORMAT_R8, 1 }, { 1, 1, DRM_FORMAT_R8, 2 } } } }; DmabufBuffer::DmabufBuffer(EGLImage image, const QVector &planes, uint32_t format, const QSize &size, Flags flags, LinuxDmabuf *interfaceImpl) : DmabufBuffer(planes, format, size, flags, interfaceImpl) { m_importType = ImportType::Direct; addImage(image); } DmabufBuffer::DmabufBuffer(const QVector &planes, uint32_t format, const QSize &size, Flags flags, LinuxDmabuf *interfaceImpl) : KWayland::Server::LinuxDmabufUnstableV1Buffer(format, size) , m_planes(planes) , m_flags(flags) , m_interfaceImpl(interfaceImpl) { m_importType = ImportType::Conversion; } DmabufBuffer::~DmabufBuffer() { if (m_interfaceImpl) { m_interfaceImpl->removeBuffer(this); removeImages(); } // Close all open file descriptors for (int i = 0; i < m_planes.count(); i++) { if (m_planes[i].fd != -1) ::close(m_planes[i].fd); m_planes[i].fd = -1; } } void DmabufBuffer::addImage(EGLImage image) { m_images << image; } void DmabufBuffer::removeImages() { for (auto image : m_images) { eglDestroyImageKHR(m_interfaceImpl->m_backend->eglDisplay(), image); } m_images.clear(); m_interfaceImpl = nullptr; } using Plane = KWayland::Server::LinuxDmabufUnstableV1Interface::Plane; using Flags = KWayland::Server::LinuxDmabufUnstableV1Interface::Flags; EGLImage LinuxDmabuf::createImage(const QVector &planes, uint32_t format, const QSize &size) { const bool hasModifiers = eglQueryDmaBufModifiersEXT != nullptr && planes[0].modifier != DRM_FORMAT_MOD_INVALID; QVector attribs; attribs << EGL_WIDTH << size.width() << EGL_HEIGHT << size.height() << EGL_LINUX_DRM_FOURCC_EXT << EGLint(format) << EGL_DMA_BUF_PLANE0_FD_EXT << planes[0].fd << EGL_DMA_BUF_PLANE0_OFFSET_EXT << EGLint(planes[0].offset) << EGL_DMA_BUF_PLANE0_PITCH_EXT << EGLint(planes[0].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(planes[0].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(planes[0].modifier >> 32); } if (planes.count() > 1) { attribs << EGL_DMA_BUF_PLANE1_FD_EXT << planes[1].fd << EGL_DMA_BUF_PLANE1_OFFSET_EXT << EGLint(planes[1].offset) << EGL_DMA_BUF_PLANE1_PITCH_EXT << EGLint(planes[1].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(planes[1].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT << EGLint(planes[1].modifier >> 32); } } if (planes.count() > 2) { attribs << EGL_DMA_BUF_PLANE2_FD_EXT << planes[2].fd << EGL_DMA_BUF_PLANE2_OFFSET_EXT << EGLint(planes[2].offset) << EGL_DMA_BUF_PLANE2_PITCH_EXT << EGLint(planes[2].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(planes[2].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT << EGLint(planes[2].modifier >> 32); } } if (eglQueryDmaBufModifiersEXT != nullptr && planes.count() > 3) { attribs << EGL_DMA_BUF_PLANE3_FD_EXT << planes[3].fd << EGL_DMA_BUF_PLANE3_OFFSET_EXT << EGLint(planes[3].offset) << EGL_DMA_BUF_PLANE3_PITCH_EXT << EGLint(planes[3].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(planes[3].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT << EGLint(planes[3].modifier >> 32); } } attribs << EGL_NONE; EGLImage image = eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data()); if (image == EGL_NO_IMAGE_KHR) { return nullptr; } return image; } KWayland::Server::LinuxDmabufUnstableV1Buffer* LinuxDmabuf::importBuffer(const QVector &planes, uint32_t format, const QSize &size, Flags flags) { Q_ASSERT(planes.count() > 0); // Try first to import as a single image if (auto *img = createImage(planes, format, size)) { return new DmabufBuffer(img, planes, format, size, flags, this); } // TODO: to enable this we must be able to store multiple textures per window pixmap // and when on window draw do yuv to rgb transformation per shader (see Weston) // // not a single image, try yuv import // return yuvImport(planes, format, size, flags); return nullptr; } KWayland::Server::LinuxDmabufUnstableV1Buffer* LinuxDmabuf::yuvImport(const QVector &planes, uint32_t format, const QSize &size, Flags flags) { YuvFormat yuvFormat; for (YuvFormat f : yuvFormats) { if (f.format == format) { yuvFormat = f; break; } } if (yuvFormat.format == 0) { return nullptr; } if (planes.count() != yuvFormat.inputPlanes) { return nullptr; } auto *buf = new DmabufBuffer(planes, format, size, flags, this); for (int i = 0; i < yuvFormat.outputPlanes; i++) { int planeIndex = yuvFormat.planes[i].planeIndex; Plane plane = { planes[planeIndex].fd, planes[planeIndex].offset, planes[planeIndex].stride, planes[planeIndex].modifier }; const auto planeFormat = yuvFormat.planes[i].format; const auto planeSize = QSize(size.width() / yuvFormat.planes[i].widthDivisor, size.height() / yuvFormat.planes[i].heightDivisor); auto *image = createImage(QVector(1, plane), planeFormat, planeSize); if (!image) { delete buf; return nullptr; } buf->addImage(image); } // TODO: add buf import properties return buf; } LinuxDmabuf* LinuxDmabuf::factory(AbstractEglBackend *backend) { if (!backend->hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import"))) { return nullptr; } if (backend->hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) { eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXT_func) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); } if (eglQueryDmaBufFormatsEXT == nullptr) { return nullptr; } return new LinuxDmabuf(backend); } LinuxDmabuf::LinuxDmabuf(AbstractEglBackend *backend) : KWayland::Server::LinuxDmabufUnstableV1Interface::Impl() , m_backend(backend) { Q_ASSERT(waylandServer()); m_interface = waylandServer()->display()->createLinuxDmabufInterface(backend); setSupportedFormatsAndModifiers(); m_interface->setImpl(this); m_interface->create(); } LinuxDmabuf::~LinuxDmabuf() { for (auto *dmabuf : qAsConst(m_buffers)) { dmabuf->removeImages(); } } void LinuxDmabuf::removeBuffer(DmabufBuffer *buffer) { m_buffers.remove(buffer); } const uint32_t s_multiPlaneFormats[] = { DRM_FORMAT_XRGB8888_A8, DRM_FORMAT_XBGR8888_A8, DRM_FORMAT_RGBX8888_A8, DRM_FORMAT_BGRX8888_A8, DRM_FORMAT_RGB888_A8, DRM_FORMAT_BGR888_A8, DRM_FORMAT_RGB565_A8, DRM_FORMAT_BGR565_A8, DRM_FORMAT_NV12, DRM_FORMAT_NV21, DRM_FORMAT_NV16, DRM_FORMAT_NV61, DRM_FORMAT_NV24, DRM_FORMAT_NV42, DRM_FORMAT_YUV410, DRM_FORMAT_YVU410, DRM_FORMAT_YUV411, DRM_FORMAT_YVU411, DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422, DRM_FORMAT_YVU422, DRM_FORMAT_YUV444, DRM_FORMAT_YVU444 }; void filterFormatsWithMultiplePlanes(QVector &formats) { QVector::iterator it = formats.begin(); while (it != formats.end()) { for (auto linuxFormat : s_multiPlaneFormats) { if (*it == linuxFormat) { qDebug() << "Filter multi-plane format" << *it; it = formats.erase(it); it--; break; } } it++; } } void LinuxDmabuf::setSupportedFormatsAndModifiers() { const EGLDisplay eglDisplay = m_backend->eglDisplay(); EGLint count = 0; EGLBoolean success = eglQueryDmaBufFormatsEXT(eglDisplay, 0, NULL, &count); if (!success || count == 0) { return; } QVector formats(count); if (!eglQueryDmaBufFormatsEXT(eglDisplay, count, (EGLint *) formats.data(), &count)) { return; } filterFormatsWithMultiplePlanes(formats); QHash > set; for (auto format : qAsConst(formats)) { if (eglQueryDmaBufModifiersEXT != nullptr) { count = 0; success = eglQueryDmaBufModifiersEXT(eglDisplay, format, 0, NULL, NULL, &count); if (success && count > 0) { QVector modifiers(count); if (eglQueryDmaBufModifiersEXT(eglDisplay, format, count, modifiers.data(), NULL, &count)) { QSet modifiersSet; for (auto mod : qAsConst(modifiers)) { modifiersSet.insert(mod); } set.insert(format, modifiersSet); continue; } } } set.insert(format, QSet()); } m_interface->setSupportedFormatsWithModifiers(set); } }