Implement hardware accelerated rendering
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This implements GL accelerated rendering using the KHR_platform_gbm EGL
extension [0] and arcan_shmifext_signal_planes().
While for offscreen surfaces we can simply delegate to QEGLPbuffer from
Qt's own EGL convenience helpers, for onscreen windows we have to add
the slightly more involved integration with GBM surfaces.

[0] https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_platform_gbm.txt
This commit is contained in:
Magnus Groß 2025-09-15 13:06:34 +02:00
commit 3e35da596b
Signed by: vimpostor
GPG key ID: 0CB6D889DA64A968
14 changed files with 332 additions and 18 deletions

View file

@ -8,13 +8,14 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package("Qt${TARGET_QT_VERSION}" CONFIG REQUIRED COMPONENTS Core Gui)
find_package("Qt${TARGET_QT_VERSION}" CONFIG REQUIRED COMPONENTS Core Gui OpenGL)
find_package(PkgConfig REQUIRED)
pkg_check_modules(arcan-shmif REQUIRED IMPORTED_TARGET arcan-shmif)
pkg_check_modules(arcan-shmif-ext REQUIRED IMPORTED_TARGET arcan-shmif-ext)
file(GLOB_RECURSE SRCS "src/*.cpp" "src/*.h")
list(APPEND LINK_LIBS Qt::Core Qt::CorePrivate Qt::Gui Qt::GuiPrivate PkgConfig::arcan-shmif)
list(APPEND LINK_LIBS Qt::Core Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::OpenGL PkgConfig::arcan-shmif PkgConfig::arcan-shmif-ext)
if(TARGET_QT_VERSION GREATER_EQUAL 6)
find_package(WrapFreetype)
@ -26,8 +27,16 @@ else()
add_library(${PROJECT_NAME} SHARED ${SRCS})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME qarcan)
find_package(Freetype)
find_package("Qt${TARGET_QT_VERSION}" CONFIG REQUIRED COMPONENTS FontDatabaseSupport EventDispatcherSupport)
list(APPEND LINK_LIBS Freetype::Freetype Qt::FontDatabaseSupportPrivate Qt::EventDispatcherSupportPrivate)
find_package("Qt${TARGET_QT_VERSION}" CONFIG REQUIRED COMPONENTS FontDatabaseSupport EventDispatcherSupport EglSupport)
list(APPEND LINK_LIBS Freetype::Freetype Qt::FontDatabaseSupportPrivate Qt::EventDispatcherSupportPrivate Qt::EglSupport)
# workaround for Qt5 not exporting QtEglSupport correctly
get_target_property(EGLSUPPORT_INCLUDES Qt5::EglSupport INTERFACE_INCLUDE_DIRECTORIES)
foreach(EGLSUPPORT_INCLUDE IN LISTS EGLSUPPORT_INCLUDES)
if(EGLSUPPORT_INCLUDE MATCHES "QtEglSupport$")
target_include_directories(${PROJECT_NAME} PRIVATE "${EGLSUPPORT_INCLUDE}/${Qt5_VERSION}")
endif()
endforeach()
endif()
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})

View file

@ -46,7 +46,8 @@ export MESA_DEBUG=1 # to debug the EGL integration
```
Each shmif segment is represented as a [QArcanConnection](src/qarcanconnection.cpp), where each event loop runs in its own thread.
The QPA entry point is [QArcanIntegration](src/qarcanintegration.cpp), which creates one main segment and requests a new segment from the main segment for each new [QArcanWindow](src/qarcanwindow.cpp). Non-accelerated drawing is done in [QArcanBackingStore](src/qarcanbackingstore.cpp).
The QPA entry point is [QArcanIntegration](src/qarcanintegration.cpp), which creates one main segment and requests a new segment from the main segment for each new [QArcanWindow](src/qarcanwindow.cpp).
Accelerated drawing is done using the [KHR_platform_gbm](https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_platform_gbm.txt) EGL extension in [QArcanGLContext](src/qarcanglcontext.cpp). For non-accelerated drawing [QArcanBackingStore](src/qarcanbackingstore.cpp) implements software rendering.
The Qt window hierarchy is mirrored as the same shmif segment hierarchy, e.g. segments belonging to popup windows are requested from its parent window segment, not from the main segment.
For an overview of the most important available QPA events you will want to look at [QWindowSystemInterface](https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qwindowsysteminterface.cpp) and [QPlatformWindow](https://code.qt.io/cgit/qt/qtbase.git/tree/src/gui/kernel/qplatformwindow.cpp).

View file

@ -5,8 +5,6 @@
#include <qpa/qplatformbackingstore.h>
#include <qpa/qplatformwindow.h>
#include "qarcanutils.h"
QT_BEGIN_NAMESPACE
class QArcanBackingStore : public QPlatformBackingStore {

View file

@ -252,6 +252,21 @@ QRect QArcanConnection::geometry() const {
return m_geometry;
}
arcan::shmifext_setup_status QArcanConnection::setupExt() {
QArcanContGuard _ {&m_cont};
if (!ok()) {
return arcan::shmifext_setup_status::SHHIFEXT_UNKNOWN;
}
m_ext = arcan::arcan_shmifext_defaults(cont());
m_ext.no_context = true;
const auto res = arcan::arcan_shmifext_setup(cont(), m_ext);
if (res != arcan::SHMIFEXT_OK) {
qCDebug(lcQpaArcanShmif(), "Failed to setup EXT: %d", res);
}
return res;
}
uint32_t QArcanConnection::nextSegmentCookie() {
// we cannot start at 0, because segments without a cookie (that Arcan pushes to us, e.g. on clipboard paste) use 0 already
static std::atomic_uint32_t nextSegmentCookie = 1;

View file

@ -54,12 +54,14 @@ public:
void run() override;
QRect geometry() const;
arcan::shmifext_setup_status setupExt();
private:
static uint32_t nextSegmentCookie();
void handleInitial();
void dispatchListeners(std::function<void(QArcanEventListener *)> fun);
arcan::arcan_shmif_cont m_cont;
struct arcan::arcan_shmifext_setup m_ext;
uint8_t m_mouseState[ASHMIF_MSTATE_SZ];
std::string m_pendingMessage;
std::vector<QArcanEventListener *> m_listeners;

110
src/qarcanglcontext.cpp Normal file
View file

@ -0,0 +1,110 @@
#include "qarcanglcontext.h"
#include "qarcanwindow.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QtGui/private/qeglpbuffer_p.h>
#else
#include <QtEglSupport/private/qeglpbuffer_p.h>
#endif
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm.h>
#include <sys/ioctl.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaArcanGLContext, "qt.qpa.arcan.glcontext")
QArcanGLContext::QArcanGLContext(QOpenGLContext *context, EGLDisplay display, struct gbm_device *gbmDevice) : QEGLPlatformContext(context->format(), context->shareHandle(), display, nullptr), m_gbmDevice(gbmDevice) {
}
QPlatformOffscreenSurface *QArcanGLContext::createOffscreenSurface(QOffscreenSurface *surface, EGLDisplay display) {
QSurfaceFormat fmt = surface->requestedFormat();
return new QEGLPbuffer(display, fmt, surface);
}
EGLSurface QArcanGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) {
if (surface->surface()->surfaceClass() == QSurface::Window) {
return static_cast<QArcanWindow *>(surface)->eglSurface();
} else {
return static_cast<QEGLPbuffer *>(surface)->pbuffer();
}
}
#define DMABUF_PLANES_LIMIT 4
// lifted from src/platform/egl-dri/egl_gbm_helper.h
bool helper_bo_dmabuf(
struct gbm_device *dev,
EGLDisplay dpy,
size_t w,
size_t h,
int fmt,
struct gbm_bo *bo,
struct arcan::shmifext_buffer_plane *planes,
size_t *n_planes) {
size_t np = gbm_bo_get_plane_count(bo);
if (np > DMABUF_PLANES_LIMIT)
return false;
uint64_t mod = gbm_bo_get_modifier(bo);
planes[0].w = w;
planes[0].h = h;
uint32_t mod_hi = mod >> 32;
uint32_t mod_lo = mod & 0xffffffff;
for (size_t i = 0; i < np; i++) {
union gbm_bo_handle h = gbm_bo_get_handle_for_plane(bo, i);
struct drm_prime_handle prime =
{.handle = h.u32, .flags = DRM_RDWR | DRM_CLOEXEC};
if (-1 == ioctl(gbm_device_get_fd(dev), DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime))
goto cleanup;
planes[i].gbm.stride = gbm_bo_get_stride_for_plane(bo, i);
planes[i].gbm.offset = gbm_bo_get_offset(bo, i);
planes[i].gbm.format = fmt;
planes[i].fd = prime.fd;
planes[i].gbm.mod_hi = mod_hi;
planes[i].gbm.mod_lo = mod_lo;
}
*n_planes = np;
return true;
cleanup:
for (size_t i = 0; i < np; i++)
if (-1 != planes[i].fd)
close(planes[i].fd);
return false;
}
void QArcanGLContext::swapBuffers(QPlatformSurface *surface) {
QEGLPlatformContext::swapBuffers(surface);
if (surface->surface()->surfaceClass() != QSurface::Window) {
return;
}
auto window = static_cast<QArcanWindow *>(surface);
QArcanContGuard _ {window->cont()->cont()};
auto gbmSurface = window->gbmSurface();
m_gbmBo = gbm_surface_lock_front_buffer(gbmSurface);
if (!m_gbmBo) {
qCWarning(lcQpaArcanGLContext(), "Failed to lock front buffer");
return;
}
uint32_t fmt = gbm_bo_get_format(m_gbmBo);
struct arcan::shmifext_buffer_plane planes[DMABUF_PLANES_LIMIT];
size_t nPlanes;
if (!helper_bo_dmabuf(m_gbmDevice, window->eglDisplay(), window->window()->width(), window->window()->height(), fmt, m_gbmBo, planes, &nPlanes)) {
qCWarning(lcQpaArcanGLContext(), "Failed to get planes");
}
arcan::arcan_shmifext_signal_planes(window->cont()->cont(), arcan::SHMIF_SIGVID, nPlanes, planes);
gbm_surface_release_buffer(gbmSurface, m_gbmBo);
}
QT_END_NAMESPACE

32
src/qarcanglcontext.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef QARCANGLCONTEXT_H
#define QARCANGLCONTEXT_H
#include "qarcanutils.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QtGui/private/qeglplatformcontext_p.h>
#else
#include <QtEglSupport/private/qeglplatformcontext_p.h>
#endif
#include <QOffscreenSurface>
#include "qarcanwindow.h"
QT_BEGIN_NAMESPACE
class QArcanGLContext : public QEGLPlatformContext {
public:
QArcanGLContext(QOpenGLContext *context, EGLDisplay display, struct gbm_device *gbmDevice);
static QPlatformOffscreenSurface *createOffscreenSurface(QOffscreenSurface *surface, EGLDisplay display);
EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) override;
void swapBuffers(QPlatformSurface *surface) override;
private:
struct gbm_device *m_gbmDevice = nullptr;
struct gbm_bo *m_gbmBo = nullptr;
};
QT_END_NAMESPACE
#endif // QARCANGLCONTEXT_H

View file

@ -34,7 +34,9 @@
#include "qarcanclipboard.h"
#include "qarcandrag.h"
#include "qarcanglcontext.h"
#include "qarcannativeinterface.h"
#include "qarcanwindow.h"
QT_BEGIN_NAMESPACE
@ -72,8 +74,12 @@ bool QArcanIntegration::hasCapability(QPlatformIntegration::Capability cap) cons
return true;
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
case RhiBasedRendering:
return false;
return true;
#endif
case OpenGL:
return true;
case ThreadedOpenGL:
return true;
default:
return QPlatformIntegration::hasCapability(cap);
}
@ -98,7 +104,11 @@ QPlatformWindow *QArcanIntegration::createPlatformWindow(QWindow *window) const
}
}
QArcanWindow *w = new QArcanWindow(window, cont, std::bind_front(&QArcanIntegration::notifyClipboard, this), m_contStealAvailable);
if (window->supportsOpenGL()) {
m_primaryScreen->setupExt();
}
QArcanWindow *w = new QArcanWindow(window, cont, std::bind_front(&QArcanIntegration::notifyClipboard, this), m_contStealAvailable, window->supportsOpenGL(), m_primaryScreen->eglDisplay(), m_primaryScreen->gbmDevice());
// Only allow single segment mode on the first window request.
// Some simple WMs may not accept_target() for subsegments in general,
// so in that case we can fallback to the main segment.
@ -116,6 +126,20 @@ QPlatformBackingStore *QArcanIntegration::createPlatformBackingStore(QWindow *wi
return new QArcanBackingStore(window);
}
QPlatformOpenGLContext *QArcanIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const {
if (!m_primaryScreen->setupExt()) {
return nullptr;
}
return new QArcanGLContext(context, m_primaryScreen->eglDisplay(), m_primaryScreen->gbmDevice());
}
QPlatformOffscreenSurface *QArcanIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const {
if (!m_primaryScreen->setupExt()) {
return nullptr;
}
return QArcanGLContext::createOffscreenSurface(surface, m_primaryScreen->eglDisplay());
}
QAbstractEventDispatcher *QArcanIntegration::createEventDispatcher() const {
return QtGenericUnixDispatcher::createUnixEventDispatcher();
}

View file

@ -4,7 +4,6 @@
#include "qarcanconnection.h"
#include "qarcanscreen.h"
#include "qarcanutils.h"
#include "qarcanwindow.h"
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformscreen.h>
@ -26,6 +25,8 @@ public:
QPlatformWindow *createPlatformWindow(QWindow *window) const override;
QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override;
QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override;
QAbstractEventDispatcher *createEventDispatcher() const override;
QPlatformClipboard *clipboard() const override;
QPlatformDrag *drag() const override;

View file

@ -57,10 +57,47 @@ void QArcanScreen::setPpcm(float ppcm) {
// sanity check that we do not get a bogus value
return;
}
m_dpi = ppcm * QArcan::cmPerInch;
const auto newDpi = ppcm * QArcan::cmPerInch;
if (newDpi == m_dpi) {
return;
}
m_dpi = newDpi;
qCDebug(lcQpaArcanScreen(), "New DPI: %f", m_dpi);
// TODO: Why does Qt not react to these DPI change events?
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(this->screen(), m_dpi, m_dpi);
}
bool QArcanScreen::setupExt() {
if (m_eglDisplay != EGL_NO_DISPLAY) {
return true;
}
int devFd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC);
if (devFd < -1) {
qCWarning(lcQpaArcanScreen(), "Failed to open render device");
return false;
}
m_gbmDevice = gbm_create_device(devFd);
if (!m_gbmDevice) {
qCWarning(lcQpaArcanScreen(), "Failed to create GBM device");
return false;
}
m_eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, m_gbmDevice, nullptr);
if (m_eglDisplay == EGL_NO_DISPLAY) {
qCWarning(lcQpaArcanScreen(), "Failed to get platform display");
return false;
}
EGLint major, minor;
return eglInitialize(m_eglDisplay, &major, &minor);
}
struct gbm_device *QArcanScreen::gbmDevice() const {
return m_gbmDevice;
}
EGLDisplay QArcanScreen::eglDisplay() const {
return m_eglDisplay;
}
QT_END_NAMESPACE

View file

@ -1,10 +1,17 @@
#ifndef QARCANSCREEN_H
#define QARCANSCREEN_H
#include "qarcanutils.h"
#include <fcntl.h>
#include <gbm.h>
#include <qpa/qplatformscreen.h>
#include "qarcanconnection.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QtGui/private/qeglconvenience_p.h>
#else
#include <QtEglSupport/private/qeglconvenience_p.h>
#endif
#include "qarcanutils.h"
QT_BEGIN_NAMESPACE
@ -22,12 +29,19 @@ public:
void setGeometry(QRect geometry);
void setRate(int rate);
void setPpcm(float ppcm);
bool setupExt();
struct gbm_device *gbmDevice() const;
EGLDisplay eglDisplay() const;
private:
QRect m_geometry {0, 0, 1920, 1080};
int m_depth = 32;
int m_rate = 60;
float m_dpi = 96;
QImage::Format m_format = QImage::Format_ARGB32_Premultiplied; // TODO: Support other formats
QImage::Format m_format = QImage::Format_ARGB32_Premultiplied;
struct gbm_device *m_gbmDevice = nullptr;
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
};
QT_END_NAMESPACE

View file

@ -1,6 +1,8 @@
#ifndef QARCANUTILS_H
#define QARCANUTILS_H
#define WANT_ARCAN_SHMIF_HELPER
#include <QLoggingCategory>
#include <qpa/qplatformscreen.h>
#include <qpa/qplatformwindow.h>

View file

@ -1,16 +1,19 @@
#include "qarcanwindow.h"
#include <gbm.h>
#include <qpa/qwindowsysteminterface.h>
#include "qarcanscreen.h"
#include <fcntl.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaArcanEvents, "qt.qpa.arcan.events")
Q_LOGGING_CATEGORY(lcQpaArcanWindow, "qt.qpa.arcan.window")
QArcanWindow::QArcanWindow(QWindow *window, std::shared_ptr<QArcanConnection> parentCont, QArcanClipboard::ClipboardHandlerFn clipboardHandler, bool maySteal)
: QPlatformWindow(window), m_parentCont(parentCont), m_clipboardCallback(clipboardHandler), m_steal(maySteal) {
QArcanWindow::QArcanWindow(QWindow *window, std::shared_ptr<QArcanConnection> parentCont, QArcanClipboard::ClipboardHandlerFn clipboardHandler, bool maySteal, bool setupExt, EGLDisplay display, struct gbm_device *gbmDevice)
: QPlatformWindow(window), m_parentCont(parentCont), m_clipboardCallback(clipboardHandler), m_steal(maySteal), m_setupExt(setupExt), m_eglDisplay(display), m_gbmDevice(gbmDevice) {
m_parentCont->addEventListener(this);
QPoint pos;
if (window->flags().testFlag(Qt::Popup)) {
@ -114,6 +117,10 @@ void QArcanWindow::handleNewSegment(uint32_t cookie, bool accepted) {
}
// cont.hints = arcan::SHMIF_RHINT_VSIGNAL_EV;
m_cont = std::make_shared<QArcanConnection>(std::move(cont), this);
if (m_setupExt) {
setupExt();
}
}
void QArcanWindow::handleMouseMoveEvent(int16_t x, int16_t y) {
@ -148,6 +155,7 @@ void QArcanWindow::handleDisplayHint(int32_t width, int32_t height, int flags, f
#endif
}
window()->resize(width, height);
m_cont->resize(width, height);
}
@ -205,8 +213,14 @@ void QArcanWindow::handleTermination() {
void QArcanWindow::handleResizeSuccess(int width, int height) {
// trigger redraw
window()->resize(width, height);
QWindowSystemInterface::handleExposeEvent(window(), QRegion(0, 0, width, height));
qCDebug(lcQpaArcanEvents(), "Resize successful");
if (m_setupExt) {
createSurface();
}
}
void QArcanWindow::requestActivateWindow() {
@ -236,7 +250,47 @@ void QArcanWindow::syncViewport(std::optional<QRect> rect, bool visible) {
QArcanContGuard _ {m_cont->cont()};
m_cont->requestViewport(m_parentCont->segmentToken(), r.x(), r.y(), r.width(), r.height(), visible);
}
m_cont->resize(r.width(), r.height(), false);
m_cont->resize(r.width(), r.height(), true);
}
}
EGLSurface QArcanWindow::eglSurface() const {
return m_eglSurface;
}
EGLDisplay QArcanWindow::eglDisplay() const {
return m_eglDisplay;
}
struct gbm_surface *QArcanWindow::gbmSurface() const {
return m_gbmSurface;
}
void QArcanWindow::setupExt() {
m_cont->setupExt();
QArcanContGuard _ {m_cont->cont()};
createSurface();
}
void QArcanWindow::createSurface() {
const auto fmt = window()->requestedFormat();
const auto display = eglDisplay();
auto config = q_configFromGLFormat(display, fmt);
EGLint nativeFormat;
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &nativeFormat);
if (m_gbmSurface) {
// gbm_surface_destroy(m_gbmSurface);
}
// create GBM surface
m_gbmSurface = gbm_surface_create(m_gbmDevice, window()->width(), window()->height(), nativeFormat, GBM_BO_USE_RENDERING);
if (!m_gbmSurface) {
qCWarning(lcQpaArcanWindow(), "Failed to create GBM surface");
return;
}
// create platform window surface
m_eglSurface = eglCreatePlatformWindowSurface(display, config, m_gbmSurface, nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
qCWarning(lcQpaArcanWindow(), "Failed to create platform surface");
}
}

View file

@ -1,6 +1,7 @@
#ifndef QARCANWINDOW_H
#define QARCANWINDOW_H
#include <EGL/egl.h>
#include <qpa/qplatformscreen.h>
#include <qpa/qplatformwindow.h>
@ -8,12 +9,13 @@
#include "qarcanconnection.h"
#include "qarcanutils.h"
QT_BEGIN_NAMESPACE
class QArcanWindow : public QPlatformWindow,
public QArcanEventListener {
public:
explicit QArcanWindow(QWindow *window, std::shared_ptr<QArcanConnection> parentCont, QArcanClipboard::ClipboardHandlerFn clipboardHandler, bool maySteal = false);
explicit QArcanWindow(QWindow *window, std::shared_ptr<QArcanConnection> parentCont, QArcanClipboard::ClipboardHandlerFn clipboardHandler, bool maySteal = false, bool setupExt = false, EGLDisplay display = EGL_NO_DISPLAY, struct gbm_device *gbmDevice = nullptr);
~QArcanWindow();
// QPlatformWindow
@ -40,10 +42,17 @@ public:
QPoint transientPos() const;
void syncViewport(std::optional<QRect> rect = std::nullopt, bool visible = true);
EGLSurface eglSurface() const;
EGLDisplay eglDisplay() const;
struct gbm_surface *gbmSurface() const;
protected:
private:
void setupExt();
void createSurface();
std::shared_ptr<QArcanConnection> m_cont;
std::shared_ptr<QArcanConnection> m_parentCont;
QOpenGLContext *m_context = nullptr;
std::unique_ptr<QArcanClipboardHandler> m_clipboardCont;
QArcanClipboard::ClipboardHandlerFn m_clipboardCallback;
uint32_t m_cookie = QArcan::invalidSegmentCookie;
@ -55,6 +64,12 @@ private:
QString m_windowtitle;
bool m_valid = true;
bool m_steal = false;
bool m_setupExt = false;
EGLSurface m_eglSurface = EGL_NO_SURFACE;
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
struct gbm_device *m_gbmDevice = nullptr;
struct gbm_surface *m_gbmSurface = nullptr;
};
QT_END_NAMESPACE