Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qeglimagefunctions_p.h"
#include <QtMultimedia/private/qmultimedia_ranges_p.h>
#include <QtCore/qloggingcategory.h>
#include <optional>
#include <vector>
#if defined(EGL_KHR_image)
# include <limits>
# include <mutex>
#endif
QT_BEGIN_NAMESPACE
namespace QtMultimediaPrivate {
namespace {
#if defined(EGL_KHR_image)
Q_STATIC_LOGGING_CATEGORY(qLcEglImage, "qt.multimedia.egl");
// eglCreateImageKHR takes an EGLint attribute list, while our callers build an EGLAttrib
// (intptr_t) list, as required by core eglCreateImage. On 64-bit platforms we need to perform
// narrowing conversion; all of our current attribute values (dimensions, fds, offsets/strides,
// and the already 32-bit-split dma-buf modifier) fit into EGLint, but we still validate this
// rather than silently truncate.
[[maybe_unused]] std::optional<std::vector<EGLint>> narrowToEGLint(QSpan<const EGLAttrib> attribs)
{
std::vector<EGLint> narrowed(size_t(attribs.size()));
Q_ASSERT(sizeof(EGLAttrib) > sizeof(EGLint));
static std::once_flag warnOnce;
std::call_once(warnOnce, [] {
qCDebug(qLcEglImage) << "EGL_KHR_image_base fallback: narrowing 64-bit EGLAttrib "
"attribute values to 32-bit EGLint";
});
for (qsizetype i = 0; i < attribs.size(); ++i) {
const EGLAttrib value = attribs[i];
if (value < std::numeric_limits<EGLint>::min()
|| value > std::numeric_limits<EGLint>::max()) {
qCWarning(qLcEglImage)
<< "EGL attribute value" << value
<< "does not fit into EGLint; cannot use the EGL_KHR_image_base fallback";
return std::nullopt;
}
narrowed[size_t(i)] = EGLint(value);
}
return narrowed;
}
#endif
template <typename Fn>
Fn getEglFunction(const char *name)
{
return reinterpret_cast<Fn>(eglGetProcAddress(name));
}
} // namespace
QEglImageFunctions::QEglImageFunctions()
:
#ifdef GL_OES_EGL_image
m_glEGLImageTargetTexture2DOES{
getEglFunction<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>("glEGLImageTargetTexture2DOES"),
}
#endif
#ifdef EGL_VERSION_1_5
,
m_eglCreateImage{
getEglFunction<PFNEGLCREATEIMAGEPROC>("eglCreateImage"),
},
m_eglDestroyImage{
getEglFunction<PFNEGLDESTROYIMAGEPROC>("eglDestroyImage"),
}
#endif
#if defined(EGL_KHR_image)
,
m_eglCreateImageKHR{
getEglFunction<PFNEGLCREATEIMAGEKHRPROC>("eglCreateImageKHR"),
},
m_eglDestroyImageKHR{
getEglFunction<PFNEGLDESTROYIMAGEKHRPROC>("eglDestroyImageKHR"),
}
#endif
#if defined(EGL_EXT_image_dma_buf_import_modifiers)
,
m_eglQueryDmaBufModifiersEXT{
getEglFunction<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>("eglQueryDmaBufModifiersEXT"),
}
#endif
{
}
const QEglImageFunctions &QEglImageFunctions::instance()
{
static const QEglImageFunctions singleton;
return singleton;
}
bool QEglImageFunctions::isValid() const
{
#ifdef GL_OES_EGL_image
if (!m_glEGLImageTargetTexture2DOES)
return false;
#else
return false;
#endif
#ifdef EGL_VERSION_1_5
if (m_eglCreateImage && m_eglDestroyImage)
return true;
#endif
#if defined(EGL_KHR_image)
if (m_eglCreateImageKHR && m_eglDestroyImageKHR)
return true;
#endif
return false;
}
void QEglImageFunctions::glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) const
{
#ifdef GL_OES_EGL_image
m_glEGLImageTargetTexture2DOES(target, image);
#endif
}
EGLImageHandle QEglImageFunctions::eglCreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target,
EGLClientBuffer buffer,
QSpan<const EGLAttrib> attribs) const
{
EglImageDeleter deleter(dpy);
#ifdef EGL_VERSION_1_5
if (m_eglCreateImage) {
return EGLImageHandle(
m_eglCreateImage(dpy, ctx, target, buffer,
attribs.empty() ? nullptr : attribs.data()),
deleter);
}
#endif
#if defined(EGL_KHR_image)
if (m_eglCreateImageKHR) {
if constexpr (sizeof(EGLAttrib) == sizeof(EGLint)) {
return EGLImageHandle(
m_eglCreateImageKHR(
dpy, ctx, target, buffer,
attribs.empty() ? nullptr
: reinterpret_cast<const EGLint *>(attribs.data())),
deleter);
} else {
const auto narrowed = narrowToEGLint(attribs);
if (!narrowed)
return EGLImageHandle(EGL_NO_IMAGE, deleter);
return EGLImageHandle(
m_eglCreateImageKHR(dpy, ctx, target, buffer,
narrowed->empty() ? nullptr : narrowed->data()),
deleter);
}
}
#endif
return EGLImageHandle(EGL_NO_IMAGE, deleter);
}
EGLBoolean QEglImageFunctions::eglDestroyImage(EGLDisplay dpy, EGLImage image) const
{
#ifdef EGL_VERSION_1_5
if (m_eglDestroyImage)
return m_eglDestroyImage(dpy, image);
#endif
#if defined(EGL_KHR_image)
if (m_eglDestroyImageKHR)
return m_eglDestroyImageKHR(dpy, image);
#endif
return EGL_FALSE;
}
std::optional<bool> QEglImageFunctions::isDmaBufModifierSupported(EGLDisplay display,
DRMFormat drmFormat,
DRMModifier modifier) const
{
#if defined(EGL_EXT_image_dma_buf_import_modifiers)
if (!m_eglQueryDmaBufModifiersEXT)
return std::nullopt; // can't introspect
const EGLint format = EGLint(qToUnderlying(drmFormat));
EGLint numModifiers = 0;
if (!m_eglQueryDmaBufModifiersEXT(display, format, 0, nullptr, nullptr, &numModifiers)
|| numModifiers <= 0)
return std::nullopt; // query unsupported
std::vector<EGLuint64KHR> modifiers(numModifiers);
if (!m_eglQueryDmaBufModifiersEXT(display, format, numModifiers, modifiers.data(), nullptr,
&numModifiers))
return std::nullopt;
return ranges::find(modifiers, EGLuint64KHR(qToUnderlying(modifier))) != modifiers.end();
#else
Q_UNUSED(display);
Q_UNUSED(drmFormat);
Q_UNUSED(modifier);
return std::nullopt;
#endif
}
} // namespace QtMultimediaPrivate
QT_END_NAMESPACE