Changeset View
Standalone View
platformsupport/scenes/opengl/abstract_egl_backend.cpp
Context not available. | |||||
36 | #include <QOpenGLContext> | 36 | #include <QOpenGLContext> | ||
---|---|---|---|---|---|
37 | #include <QOpenGLFramebufferObject> | 37 | #include <QOpenGLFramebufferObject> | ||
38 | 38 | | |||
39 | #include <unistd.h> | ||||
40 | | ||||
39 | #include <memory> | 41 | #include <memory> | ||
40 | 42 | | |||
41 | namespace KWin | 43 | namespace KWin | ||
Context not available. | |||||
48 | eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr; | 50 | eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr; | ||
49 | eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr; | 51 | eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr; | ||
50 | 52 | | |||
53 | typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); | ||||
54 | typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); | ||||
55 | eglQueryDmaBufFormatsEXT_func eglQueryDmaBufFormatsEXT = nullptr; | ||||
56 | eglQueryDmaBufModifiersEXT_func eglQueryDmaBufModifiersEXT = nullptr; | ||||
57 | | ||||
51 | #ifndef EGL_WAYLAND_BUFFER_WL | 58 | #ifndef EGL_WAYLAND_BUFFER_WL | ||
52 | #define EGL_WAYLAND_BUFFER_WL 0x31D5 | 59 | #define EGL_WAYLAND_BUFFER_WL 0x31D5 | ||
53 | #endif | 60 | #endif | ||
Context not available. | |||||
58 | #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB | 65 | #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB | ||
59 | #endif | 66 | #endif | ||
60 | 67 | | |||
68 | #ifndef EGL_EXT_image_dma_buf_import | ||||
69 | #define EGL_LINUX_DMA_BUF_EXT 0x3270 | ||||
70 | #define EGL_LINUX_DRM_FOURCC_EXT 0x3271 | ||||
71 | #define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 | ||||
72 | #define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 | ||||
73 | #define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 | ||||
74 | #define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 | ||||
75 | #define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 | ||||
76 | #define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 | ||||
77 | #define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 | ||||
78 | #define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 | ||||
79 | #define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A | ||||
80 | #define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B | ||||
81 | #define EGL_SAMPLE_RANGE_HINT_EXT 0x327C | ||||
82 | #define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D | ||||
83 | #define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E | ||||
84 | #define EGL_ITU_REC601_EXT 0x327F | ||||
85 | #define EGL_ITU_REC709_EXT 0x3280 | ||||
86 | #define EGL_ITU_REC2020_EXT 0x3281 | ||||
87 | #define EGL_YUV_FULL_RANGE_EXT 0x3282 | ||||
88 | #define EGL_YUV_NARROW_RANGE_EXT 0x3283 | ||||
89 | #define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 | ||||
90 | #define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 | ||||
91 | #endif // EGL_EXT_image_dma_buf_import | ||||
92 | | ||||
93 | #ifndef EGL_EXT_image_dma_buf_import_modifiers | ||||
94 | #define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 | ||||
95 | #define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 | ||||
96 | #define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 | ||||
97 | #define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 | ||||
98 | #define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 | ||||
99 | #define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 | ||||
100 | #define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 | ||||
101 | #define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 | ||||
102 | #define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 | ||||
103 | #define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 | ||||
104 | #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A | ||||
105 | #endif // EGL_EXT_image_dma_buf_import_modifiers | ||||
106 | | ||||
61 | AbstractEglBackend::AbstractEglBackend() | 107 | AbstractEglBackend::AbstractEglBackend() | ||
62 | : QObject(nullptr) | 108 | : QObject(nullptr) | ||
63 | , OpenGLBackend() | 109 | , OpenGLBackend() | ||
Context not available. | |||||
65 | connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::unbindWaylandDisplay); | 111 | connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::unbindWaylandDisplay); | ||
66 | } | 112 | } | ||
67 | 113 | | |||
68 | AbstractEglBackend::~AbstractEglBackend() = default; | 114 | AbstractEglBackend::~AbstractEglBackend() | ||
115 | { | ||||
116 | for (auto *dmabuf : qAsConst(m_dmabufBuffers)) { | ||||
117 | dmabuf->destroyImage(); | ||||
anthonyfieroni: Call delete dmabuf will have same effect. | |||||
No, it will not. It will leave a dangling pointer in the wl_resource, which will result in a double-free when the client deletes the buffer. Or a segfault the next time it tries to attach the buffer to a surface. I will expand on this in a separate comment. fredrik: No, it will not. It will leave a dangling pointer in the wl_resource, which will result in a… | |||||
118 | } | ||||
119 | } | ||||
69 | 120 | | |||
70 | void AbstractEglBackend::unbindWaylandDisplay() | 121 | void AbstractEglBackend::unbindWaylandDisplay() | ||
71 | { | 122 | { | ||
Context not available. | |||||
169 | } | 220 | } | ||
170 | } | 221 | } | ||
171 | } | 222 | } | ||
223 | | ||||
224 | if (hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) { | ||||
225 | eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); | ||||
226 | eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXT_func) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); | ||||
227 | } | ||||
228 | | ||||
229 | m_haveDmabufImport = hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import")); | ||||
172 | } | 230 | } | ||
173 | 231 | | |||
174 | void AbstractEglBackend::initClientExtensions() | 232 | void AbstractEglBackend::initClientExtensions() | ||
Context not available. | |||||
285 | kwinApp()->platform()->setSceneEglSurface(surface); | 343 | kwinApp()->platform()->setSceneEglSurface(surface); | ||
286 | } | 344 | } | ||
287 | 345 | | |||
346 | void AbstractEglBackend::aboutToDestroy(EglDmabufBuffer *buffer) | ||||
Name should be more descriptive in relation to functionality, also it's not a signal, so "aboutTo" imo not recommended. Suggestion: removeDmabufBuffer romangg: Name should be more descriptive in relation to functionality, also it's not a signal, so… | |||||
347 | { | ||||
348 | m_dmabufBuffers.removeOne(buffer); | ||||
349 | } | ||||
350 | | ||||
351 | QVector<uint32_t> AbstractEglBackend::supportedDrmFormats() | ||||
352 | { | ||||
353 | if (!m_haveDmabufImport || eglQueryDmaBufFormatsEXT == nullptr) | ||||
354 | return QVector<uint32_t>(); | ||||
355 | | ||||
356 | EGLint count = 0; | ||||
357 | EGLBoolean success = eglQueryDmaBufFormatsEXT(m_display, 0, NULL, &count); | ||||
358 | | ||||
359 | if (success && count > 0) { | ||||
360 | QVector<uint32_t> formats(count); | ||||
361 | if (eglQueryDmaBufFormatsEXT(m_display, count, (EGLint *) formats.data(), &count)) { | ||||
362 | return formats; | ||||
363 | } | ||||
364 | } | ||||
365 | | ||||
366 | return QVector<uint32_t>(); | ||||
367 | } | ||||
368 | | ||||
369 | QVector<uint64_t> AbstractEglBackend::supportedDrmModifiers(uint32_t format) | ||||
370 | { | ||||
371 | if (!m_haveDmabufImport || eglQueryDmaBufModifiersEXT == nullptr) | ||||
372 | return QVector<uint64_t>(); | ||||
373 | | ||||
374 | EGLint count = 0; | ||||
375 | EGLBoolean success = eglQueryDmaBufModifiersEXT(m_display, format, 0, NULL, NULL, &count); | ||||
376 | | ||||
377 | if (success && count > 0) { | ||||
378 | QVector<uint64_t> modifiers(count); | ||||
379 | if (eglQueryDmaBufModifiersEXT(m_display, format, count, modifiers.data(), NULL, &count)) { | ||||
380 | return modifiers; | ||||
381 | } | ||||
382 | } | ||||
383 | | ||||
384 | return QVector<uint64_t>(); | ||||
385 | } | ||||
386 | | ||||
387 | KWayland::Server::LinuxDmabuf::Buffer *AbstractEglBackend::importDmabufBuffer(const QVector<KWayland::Server::LinuxDmabuf::Plane> &planes, | ||||
388 | uint32_t format, | ||||
389 | const QSize &size, | ||||
390 | KWayland::Server::LinuxDmabuf::Flags flags) | ||||
391 | { | ||||
392 | if (!m_haveDmabufImport) | ||||
393 | return nullptr; | ||||
394 | | ||||
395 | // FIXME: Add support for multi-planar images | ||||
396 | if (planes.count() != 1) | ||||
397 | return nullptr; | ||||
398 | | ||||
399 | const EGLint attribs[] = { | ||||
400 | EGL_WIDTH, size.width(), | ||||
401 | EGL_HEIGHT, size.height(), | ||||
402 | EGL_LINUX_DRM_FOURCC_EXT, EGLint(format), | ||||
403 | EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd, | ||||
404 | EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGLint(planes[0].offset), | ||||
405 | EGL_DMA_BUF_PLANE0_PITCH_EXT, EGLint(planes[0].stride), | ||||
romangg: rm | |||||
romangg: Nope, better not. Overlooked the `m_`. | |||||
406 | EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGLint(planes[0].modifier & 0xffffffff), | ||||
407 | EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGLint(planes[0].modifier >> 32), | ||||
408 | EGL_NONE | ||||
409 | }; | ||||
410 | | ||||
411 | // Note that the EGLImage does NOT take onwership of the file descriptors | ||||
412 | EGLImage image = eglCreateImageKHR(m_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs); | ||||
413 | if (image == EGL_NO_IMAGE_KHR) | ||||
414 | return nullptr; | ||||
415 | | ||||
416 | EglDmabufBuffer *buffer = new EglDmabufBuffer(image, planes, format, size, flags, this); | ||||
417 | | ||||
418 | // We keep a list of the buffers we have imported so we can clean up the EGL images | ||||
419 | // from the AbstractEglBackend destructor. | ||||
420 | m_dmabufBuffers.append(buffer); | ||||
421 | | ||||
422 | return buffer; | ||||
423 | } | ||||
424 | | ||||
425 | | ||||
426 | | ||||
427 | // -------------------------------------------------------------------- | ||||
zzag: What's holding us from doing that? | |||||
We would need to create a separate EGL image and a separate texture for each plane. fredrik: We would need to create a separate EGL image and a separate texture for each plane.
The scene… | |||||
Thanks for the explanation @fredrik. It wasn't clear to me how multi-planes buffers are handled (also that it's the same notion as for hw-planes is annoying!). Until we support multi-plane buffers shouldn't we then filter out all multi-plane formats in the supportedFormats call such that the client does not even try to use these instead of just failing with null here? romangg: Thanks for the explanation @fredrik. It wasn't clear to me how multi-planes buffers are handled… | |||||
428 | | ||||
429 | | ||||
430 | | ||||
288 | AbstractEglTexture::AbstractEglTexture(SceneOpenGLTexture *texture, AbstractEglBackend *backend) | 431 | AbstractEglTexture::AbstractEglTexture(SceneOpenGLTexture *texture, AbstractEglBackend *backend) | ||
289 | : SceneOpenGLTexturePrivate() | 432 | : SceneOpenGLTexturePrivate() | ||
290 | , q(texture) | 433 | , q(texture) | ||
Perhaps we need to add modifier only if EGL_EXT_image_dma_buf_import_modifiers is present. zzag: Perhaps we need to add modifier only if EGL_EXT_image_dma_buf_import_modifiers is present. | |||||
Context not available. | |||||
319 | if (auto s = pixmap->surface()) { | 462 | if (auto s = pixmap->surface()) { | ||
320 | s->resetTrackedDamage(); | 463 | s->resetTrackedDamage(); | ||
321 | } | 464 | } | ||
322 | if (buffer->shmBuffer()) { | 465 | if (buffer->linuxDmabufBuffer()) { | ||
466 | return loadDmabufTexture(buffer); | ||||
467 | } else if (buffer->shmBuffer()) { | ||||
323 | return loadShmTexture(buffer); | 468 | return loadShmTexture(buffer); | ||
324 | } else { | | |||
325 | return loadEglTexture(buffer); | | |||
326 | } | 469 | } | ||
470 | | ||||
471 | return loadEglTexture(buffer); | ||||
327 | } | 472 | } | ||
328 | 473 | | |||
329 | void AbstractEglTexture::updateTexture(WindowPixmap *pixmap) | 474 | void AbstractEglTexture::updateTexture(WindowPixmap *pixmap) | ||
Context not available. | |||||
340 | return; | 485 | return; | ||
341 | } | 486 | } | ||
342 | auto s = pixmap->surface(); | 487 | auto s = pixmap->surface(); | ||
488 | if (EglDmabufBuffer *dmabuf = static_cast<EglDmabufBuffer *>(buffer->linuxDmabufBuffer())) { | ||||
489 | q->bind(); | ||||
490 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->image()); | ||||
491 | q->unbind(); | ||||
492 | if (m_image != EGL_NO_IMAGE_KHR) { | ||||
493 | eglDestroyImageKHR(m_backend->eglDisplay(), m_image); | ||||
494 | } | ||||
495 | m_image = EGL_NO_IMAGE_KHR; // The wl_buffer has ownership of the image | ||||
496 | // The origin in a dmabuf-buffer is at the upper-left corner, so the meaning | ||||
497 | // of Y-inverted is the inverse of OpenGL. | ||||
498 | const bool yInverted = !(dmabuf->flags() & KWayland::Server::LinuxDmabuf::YInverted); | ||||
499 | if (m_size != dmabuf->size() || yInverted != q->isYInverted()) { | ||||
500 | m_size = dmabuf->size(); | ||||
501 | q->setYInverted(yInverted); | ||||
502 | } | ||||
503 | if (s) { | ||||
504 | s->resetTrackedDamage(); | ||||
505 | } | ||||
506 | return; | ||||
507 | } | ||||
343 | if (!buffer->shmBuffer()) { | 508 | if (!buffer->shmBuffer()) { | ||
344 | q->bind(); | 509 | q->bind(); | ||
345 | EGLImageKHR image = attach(buffer); | 510 | EGLImageKHR image = attach(buffer); | ||
346 | q->unbind(); | 511 | q->unbind(); | ||
347 | if (image != EGL_NO_IMAGE_KHR) { | 512 | if (image != EGL_NO_IMAGE_KHR) { | ||
348 | eglDestroyImageKHR(m_backend->eglDisplay(), m_image); | 513 | if (m_image != EGL_NO_IMAGE_KHR) { | ||
514 | eglDestroyImageKHR(m_backend->eglDisplay(), m_image); | ||||
515 | } | ||||
349 | m_image = image; | 516 | m_image = image; | ||
350 | } | 517 | } | ||
351 | if (s) { | 518 | if (s) { | ||
Context not available. | |||||
465 | return true; | 632 | return true; | ||
466 | } | 633 | } | ||
467 | 634 | | |||
635 | bool AbstractEglTexture::loadDmabufTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) | ||||
636 | { | ||||
637 | EglDmabufBuffer *dmabuf = static_cast<EglDmabufBuffer *>(buffer->linuxDmabufBuffer()); | ||||
638 | if (!dmabuf || dmabuf->image() == EGL_NO_IMAGE_KHR) { | ||||
639 | qCritical(KWIN_OPENGL) << "Invalid dmabuf-based wl_buffer"; | ||||
640 | q->discard(); | ||||
641 | return false; | ||||
642 | } | ||||
643 | | ||||
644 | assert(m_image == EGL_NO_IMAGE_KHR); | ||||
645 | | ||||
646 | glGenTextures(1, &m_texture); | ||||
647 | q->setWrapMode(GL_CLAMP_TO_EDGE); | ||||
648 | q->setFilter(GL_NEAREST); | ||||
649 | q->bind(); | ||||
650 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->image()); | ||||
651 | q->unbind(); | ||||
652 | | ||||
653 | m_size = dmabuf->size(); | ||||
654 | q->setYInverted(!(dmabuf->flags() & KWayland::Server::LinuxDmabuf::YInverted)); | ||||
655 | | ||||
656 | return true; | ||||
657 | } | ||||
658 | | ||||
468 | EGLImageKHR AbstractEglTexture::attach(const QPointer< KWayland::Server::BufferInterface > &buffer) | 659 | EGLImageKHR AbstractEglTexture::attach(const QPointer< KWayland::Server::BufferInterface > &buffer) | ||
469 | { | 660 | { | ||
470 | EGLint format, yInverted; | 661 | EGLint format, yInverted; | ||
Context not available. | |||||
507 | return true; | 698 | return true; | ||
508 | } | 699 | } | ||
509 | 700 | | |||
701 | | ||||
702 | | ||||
703 | // -------------------------------------------------------------------- | ||||
704 | | ||||
705 | | ||||
706 | | ||||
707 | EglDmabufBuffer::EglDmabufBuffer(EGLImage image, | ||||
708 | const QVector<KWayland::Server::LinuxDmabuf::Plane> &planes, | ||||
709 | uint32_t format, | ||||
710 | const QSize &size, | ||||
711 | KWayland::Server::LinuxDmabuf::Flags flags, | ||||
712 | AbstractEglBackend *backend) | ||||
713 | : KWayland::Server::LinuxDmabuf::Buffer(format, size), | ||||
714 | m_backend(backend), | ||||
715 | m_image(image), | ||||
716 | m_planes(planes), | ||||
717 | m_flags(flags) | ||||
718 | { | ||||
510 | } | 719 | } | ||
511 | 720 | | |||
721 | EglDmabufBuffer::~EglDmabufBuffer() | ||||
722 | { | ||||
723 | if (m_backend) { | ||||
724 | m_backend->aboutToDestroy(this); | ||||
725 | | ||||
726 | assert(m_image != EGL_NO_IMAGE_KHR); | ||||
727 | eglDestroyImageKHR(m_backend->eglDisplay(), m_image); | ||||
728 | } | ||||
729 | | ||||
730 | // Close all open file descriptors | ||||
731 | for (int i = 0; i < m_planes.count(); i++) { | ||||
732 | if (m_planes[i].fd != -1) | ||||
733 | ::close(m_planes[i].fd); | ||||
734 | m_planes[i].fd = -1; | ||||
735 | } | ||||
736 | } | ||||
737 | | ||||
738 | void EglDmabufBuffer::destroyImage() | ||||
anthonyfieroni: This function is useless. | |||||
739 | { | ||||
740 | assert(m_image != EGL_NO_IMAGE_KHR); | ||||
741 | eglDestroyImageKHR(m_backend->eglDisplay(), m_image); | ||||
742 | m_image = EGL_NO_IMAGE_KHR; | ||||
743 | m_backend = nullptr; | ||||
744 | } | ||||
745 | | ||||
746 | | ||||
747 | } // namespace KWin | ||||
748 | | ||||
Context not available. |
Call delete dmabuf will have same effect.