Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qeglfskmsgbmscreen.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
3// Copyright (C) 2016 Pelagicore AG
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:significant reason:default
6
10
11#include <private/qeglfsintegration_p.h>
12#include <private/qeglfskmsintegration_p.h>
13
14#include <QtCore/QLoggingCategory>
15
16#include <QtGui/private/qguiapplication_p.h>
17#include <QtGui/private/qtguiglobal_p.h>
18#include <QtFbSupport/private/qfbvthandler_p.h>
19
20#include <errno.h>
21
22QT_BEGIN_NAMESPACE
23
24QMutex QEglFSKmsGbmScreen::s_nonThreadedFlipMutex;
25
26static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat)
27{
28 Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
29 return drmFormat;
30}
31
32static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat)
33{
34 Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
35 return gbmFormat;
36}
37
38void QEglFSKmsGbmScreen::bufferDestroyedHandler(gbm_bo *bo, void *data)
39{
40 FrameBuffer *fb = static_cast<FrameBuffer *>(data);
41
42 if (fb->fb) {
43 gbm_device *device = gbm_bo_get_device(bo);
44 drmModeRmFB(gbm_device_get_fd(device), fb->fb);
45 }
46
47 delete fb;
48}
49
50QEglFSKmsGbmScreen::FrameBuffer *QEglFSKmsGbmScreen::framebufferForBufferObject(gbm_bo *bo)
51{
52 {
53 FrameBuffer *fb = static_cast<FrameBuffer *>(gbm_bo_get_user_data(bo));
54 if (fb)
55 return fb;
56 }
57
58 uint32_t width = gbm_bo_get_width(bo);
59 uint32_t height = gbm_bo_get_height(bo);
60 uint32_t handles[4] = { gbm_bo_get_handle(bo).u32 };
61 uint32_t strides[4] = { gbm_bo_get_stride(bo) };
62 uint32_t offsets[4] = { 0 };
63 uint32_t pixelFormat = gbmFormatToDrmFormat(gbm_bo_get_format(bo));
64
65 auto fb = std::make_unique<FrameBuffer>();
66 qCDebug(qLcEglfsKmsDebug, "Adding FB, size %ux%u, DRM format 0x%x, stride %u, handle %u",
67 width, height, pixelFormat, strides[0], handles[0]);
68
69 int ret = drmModeAddFB2(device()->fd(), width, height, pixelFormat,
70 handles, strides, offsets, &fb->fb, 0);
71
72 if (ret) {
73 qWarning("Failed to create KMS FB!");
74 return nullptr;
75 }
76
77 auto res = fb.get();
78 gbm_bo_set_user_data(bo, fb.release(), bufferDestroyedHandler);
79 return res;
80}
81
82QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QEglFSKmsDevice *device, const QKmsOutput &output, bool headless)
83 : QEglFSKmsScreen(device, output, headless)
84 , m_gbm_surface(nullptr)
85 , m_gbm_bo_current(nullptr)
86 , m_gbm_bo_next(nullptr)
87 , m_flipPending(false)
88 , m_cursor(nullptr)
89 , m_cloneSource(nullptr)
90{
91}
92
93QEglFSKmsGbmScreen::~QEglFSKmsGbmScreen()
94{
95 const int remainingScreenCount = qGuiApp->screens().count();
96 qCDebug(qLcEglfsKmsDebug, "Screen dtor. %p Remaining screens: %d", this, remainingScreenCount);
97 if (!remainingScreenCount && !device()->screenConfig()->separateScreens())
98 static_cast<QEglFSKmsGbmDevice *>(device())->destroyGlobalCursor();
99
100 if (m_cloneSource) {
101 // Remove this screen from the screen that has it as a clone destination
102 QList<CloneDestination> &dests = m_cloneSource->m_cloneDests;
103 auto newEnd = std::remove_if(dests.begin(), dests.end(),
104 [this](CloneDestination &dest) {
105 return dest.screen == this;
106 });
107 dests.erase(newEnd, dests.end());
108 }
109
110 // Other screens can no longer have this screen as a clone source
111 for (CloneDestination &dest : m_cloneDests) {
112 dest.screen->m_cloneSource = nullptr;
113 // Mode must be set again before flipping
114 dest.screen->m_output.mode_set = false;
115 }
116}
117
118QPlatformCursor *QEglFSKmsGbmScreen::cursor() const
119{
120 QKmsScreenConfig *config = device()->screenConfig();
121 if (config->headless())
122 return nullptr;
123 if (config->hwCursor()) {
124 if (!config->separateScreens())
125 return static_cast<QEglFSKmsGbmDevice *>(device())->globalCursor();
126
127 if (m_cursor.isNull()) {
128 QEglFSKmsGbmScreen *that = const_cast<QEglFSKmsGbmScreen *>(this);
129 that->m_cursor.reset(new QEglFSKmsGbmCursor(that));
130 }
131
132 return m_cursor.data();
133 } else {
134 return QEglFSScreen::cursor();
135 }
136}
137
138gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
139{
140 if (!m_gbm_surface) {
141 qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s", qPrintable(name()));
142
143 const auto gbmDevice = static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice();
144 // If there was no format override given in the config file,
145 // query the native (here, gbm) format from the EGL config.
146 const bool queryFromEgl = !m_output.drm_format_requested_by_user;
147 if (queryFromEgl) {
148 EGLint native_format = -1;
149 EGLBoolean success = eglGetConfigAttrib(display(), eglConfig, EGL_NATIVE_VISUAL_ID, &native_format);
150 qCDebug(qLcEglfsKmsDebug) << "Got native format" << Qt::hex << native_format << Qt::dec
151 << "from eglGetConfigAttrib() with return code" << bool(success);
152
153 if (success) {
154 m_gbm_surface = gbm_surface_create(gbmDevice,
155 rawGeometry().width(),
156 rawGeometry().height(),
157 native_format,
158 gbmFlags());
159 if (m_gbm_surface)
160 m_output.drm_format = gbmFormatToDrmFormat(native_format);
161 }
162 }
163
164 const uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format);
165
166 // Fallback for older drivers, and when "format" is explicitly specified
167 // in the output config. (not guaranteed that the requested format works
168 // of course, but do what we are told to)
169 if (!m_gbm_surface) {
170 if (queryFromEgl)
171 qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x", gbmFormat);
172 m_gbm_surface = gbm_surface_create(gbmDevice,
173 rawGeometry().width(),
174 rawGeometry().height(),
175 gbmFormat,
176 gbmFlags());
177 }
178
179#ifndef Q_OS_VXWORKS
180 // Fallback for some drivers, its required to request with modifiers
181 if (!m_gbm_surface) {
182 uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
183
184 m_gbm_surface = gbm_surface_create_with_modifiers(gbmDevice,
185 rawGeometry().width(),
186 rawGeometry().height(),
187 gbmFormat,
188 &modifier, 1);
189 }
190#endif
191 // Fail here, as it would fail with the next usage of the GBM surface, which is very unexpected
192 if (!m_gbm_surface)
193 qFatal("Could not create GBM surface!");
194 }
195 return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface()
196}
197
198void QEglFSKmsGbmScreen::resetSurface()
199{
200 m_flipPending = false; // not necessarily true but enough to keep bo_next
201 m_gbm_bo_current = nullptr;
202 m_gbm_surface = nullptr;
203
204 // Leave m_gbm_bo_next untouched. waitForFlip() should
205 // still do its work, when called. Otherwise we end up
206 // in device-is-busy errors if there is a new QWindow
207 // created afterwards. (QTBUG-122663)
208
209 // If not using atomic, will need a new drmModeSetCrtc if a new window
210 // gets created later on (and so there's a new fb).
211 if (!device()->hasAtomicSupport())
212 needsNewModeSetForNextFb = true;
213}
214
215void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones,
216 const QList<QPlatformScreen *> &screensCloningThisScreen)
217{
218 // clone destinations need to know the clone source
219 const bool clonesAnother = screenThisScreenClones != nullptr;
220 if (clonesAnother && !screensCloningThisScreen.isEmpty()) {
221 qWarning("QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time", qPrintable(name()));
222 return;
223 }
224 if (clonesAnother) {
225 m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones);
226 qCDebug(qLcEglfsKmsDebug, "Screen %s clones %s", qPrintable(name()), qPrintable(m_cloneSource->name()));
227 } else {
228 m_cloneSource = nullptr;
229 }
230
231 // clone sources need to know their additional destinations
232 m_cloneDests.clear();
233 for (QPlatformScreen *s : screensCloningThisScreen) {
234 CloneDestination d;
235 d.screen = static_cast<QEglFSKmsGbmScreen *>(s);
236 m_cloneDests.append(d);
237 }
238}
239
240void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
241{
242 QKmsOutput &op(output());
243 const int fd = device()->fd();
244
245 if (!op.mode_set || needsNewModeSetForNextFb) {
246 op.mode_set = true;
247 needsNewModeSetForNextFb = false;
248
249 bool doModeSet = true;
250 drmModeCrtcPtr currentMode = drmModeGetCrtc(fd, op.crtc_id);
251 const bool alreadySet = currentMode && currentMode->buffer_id == fb && !memcmp(&currentMode->mode, &op.modes[op.mode], sizeof(drmModeModeInfo));
252 if (currentMode)
253 drmModeFreeCrtc(currentMode);
254 if (alreadySet)
255 doModeSet = false;
256
257 if (doModeSet) {
258 qCDebug(qLcEglfsKmsDebug, "Setting mode for screen %s", qPrintable(name()));
259
260 if (device()->hasAtomicSupport()) {
261#if QT_CONFIG(drm_atomic)
262 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
263 if (request) {
264 drmModeAtomicAddProperty(request, op.connector_id, op.crtcIdPropertyId, op.crtc_id);
265 drmModeAtomicAddProperty(request, op.crtc_id, op.modeIdPropertyId, op.mode_blob_id);
266 drmModeAtomicAddProperty(request, op.crtc_id, op.activePropertyId, 1);
267 }
268#endif
269 } else {
270 int ret = drmModeSetCrtc(fd,
271 op.crtc_id,
272 fb,
273 0, 0,
274 &op.connector_id, 1,
275 &op.modes[op.mode]);
276
277 if (ret == 0)
278 setPowerState(PowerStateOn);
279 else
280 qErrnoWarning(errno, "Could not set DRM mode for screen %s", qPrintable(name()));
281 }
282 }
283 }
284}
285
286void QEglFSKmsGbmScreen::nonThreadedPageFlipHandler(int fd,
287 unsigned int sequence,
288 unsigned int tv_sec,
289 unsigned int tv_usec,
290 void *user_data)
291{
292 // note that with cloning involved this callback is called also for screens that clone another one
293 Q_UNUSED(fd);
294 QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data);
295 // The screen might have been deleted when DRM calls this handler
296 if (QEglFSKmsScreen::isScreenKnown(screen)) {
297 screen->flipFinished();
298 screen->pageFlipped(sequence, tv_sec, tv_usec);
299 }
300}
301
302void QEglFSKmsGbmScreen::waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen)
303{
304 m_flipMutex.lock();
305 QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
306 dev->eventReader()->startWaitFlip(screen, &m_flipMutex, &m_flipCond);
307
308 // We should only wait forever on this screen, clones should have a timeout
309 // (e.g. I clone might have been created just before the flip,
310 // we might wait for it but it might not know about waking us up)
311 bool succ = false;
312 if (screen == this)
313 succ = m_flipCond.wait(&m_flipMutex);
314 else
315 succ = m_flipCond.wait(&m_flipMutex, 300);
316
317 if (!succ)
318 qCWarning(qLcEglfsKmsDebug) << "timeout on waitForFlipWithEventReader, screen to wait for:"
319 << screen << ", screen waiting (shouldn't be the same screen):"
320 << this;
321
322 m_flipMutex.unlock();
323 screen->flipFinished();
324}
325
326void QEglFSKmsGbmScreen::waitForFlip()
327{
328 if (m_headless || m_cloneSource)
329 return;
330
331 // Don't lock the mutex unless we actually need to
332 if (!m_gbm_bo_next)
333 return;
334
335 QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
336 if (dev->usesEventReader()) {
337 waitForFlipWithEventReader(this);
338 // Now, unlike on the other code path, we need to ensure the
339 // flips have completed for the screens that just scan out
340 // this one's content, because the eventReader's wait is
341 // per-output.
342 for (CloneDestination &d : m_cloneDests) {
343 if (d.screen != this)
344 waitForFlipWithEventReader(d.screen);
345 }
346 } else {
347 QMutexLocker lock(&s_nonThreadedFlipMutex);
348 while (m_gbm_bo_next) {
349 drmEventContext drmEvent;
350 memset(&drmEvent, 0, sizeof(drmEvent));
351 drmEvent.version = 2;
352 drmEvent.vblank_handler = nullptr;
353 drmEvent.page_flip_handler = nonThreadedPageFlipHandler;
354 drmHandleEvent(device()->fd(), &drmEvent);
355 }
356 }
357
358#if QT_CONFIG(drm_atomic)
359 device()->threadLocalAtomicReset();
360#endif
361}
362
363#if QT_CONFIG(drm_atomic)
364static void addAtomicFlip(drmModeAtomicReq *request, const QKmsOutput &output, uint32_t fb)
365{
366 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
367 output.eglfs_plane->framebufferPropertyId, fb);
368
369 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
370 output.eglfs_plane->crtcPropertyId, output.crtc_id);
371
372 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
373 output.eglfs_plane->srcwidthPropertyId, output.size.width() << 16);
374
375 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
376 output.eglfs_plane->srcXPropertyId, 0);
377
378 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
379 output.eglfs_plane->srcYPropertyId, 0);
380
381 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
382 output.eglfs_plane->srcheightPropertyId, output.size.height() << 16);
383
384 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
385 output.eglfs_plane->crtcXPropertyId, 0);
386
387 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
388 output.eglfs_plane->crtcYPropertyId, 0);
389
390 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
391 output.eglfs_plane->crtcwidthPropertyId, output.modes[output.mode].hdisplay);
392
393 drmModeAtomicAddProperty(request, output.eglfs_plane->id,
394 output.eglfs_plane->crtcheightPropertyId, output.modes[output.mode].vdisplay);
395}
396#endif
397
398void QEglFSKmsGbmScreen::flip()
399{
400 // For headless or cloned screen just return silently. It is not necessarily an error
401 // to end up here, so show no warnings.
402 if (m_headless || m_cloneSource)
403 return;
404
405 if (!m_gbm_surface) {
406 qWarning("Cannot sync before platform init!");
407 return;
408 }
409
410 m_gbm_bo_next = gbm_surface_lock_front_buffer(m_gbm_surface);
411 if (!m_gbm_bo_next) {
412 qWarning("Could not lock GBM surface front buffer for screen %s", qPrintable(name()));
413 return;
414 }
415
416 auto gbmRelease = qScopeGuard([this]{
417 m_flipPending = false;
418 gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next);
419 m_gbm_bo_next = nullptr;
420 });
421
422 FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next);
423 if (!fb) {
424 qWarning("FrameBuffer not available. Cannot flip");
425 return;
426 }
427 ensureModeSet(fb->fb);
428
429 const QKmsOutput &thisOutput(output());
430 const int fd = device()->fd();
431 m_flipPending = true;
432
433 if (device()->hasAtomicSupport()) {
434#if QT_CONFIG(drm_atomic)
435 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
436 if (request) {
437 addAtomicFlip(request, thisOutput, fb->fb);
438 static int zpos = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_ZPOS");
439 if (zpos) {
440 drmModeAtomicAddProperty(request, thisOutput.eglfs_plane->id,
441 thisOutput.eglfs_plane->zposPropertyId, zpos);
442 }
443 static uint blendOp = uint(qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_BLEND_OP"));
444 if (blendOp) {
445 drmModeAtomicAddProperty(request, thisOutput.eglfs_plane->id,
446 thisOutput.eglfs_plane->blendOpPropertyId, blendOp);
447 }
448 }
449#endif
450 } else {
451 int ret = drmModePageFlip(fd,
452 thisOutput.crtc_id,
453 fb->fb,
454 DRM_MODE_PAGE_FLIP_EVENT,
455 this);
456 if (ret) {
457 qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name()));
458 return;
459 }
460 }
461
462 for (CloneDestination &d : m_cloneDests) {
463 if (d.screen != this) {
464 d.screen->ensureModeSet(fb->fb);
465 d.cloneFlipPending = true;
466 const QKmsOutput &destOutput(d.screen->output());
467
468 if (device()->hasAtomicSupport()) {
469#if QT_CONFIG(drm_atomic)
470 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
471 if (request)
472 addAtomicFlip(request, destOutput, fb->fb);
473
474 // ### This path is broken. On the other branch we can easily
475 // pass in d.screen as the user_data for drmModePageFlip, but
476 // using one atomic request breaks down here since we get events
477 // with the same user_data passed to drmModeAtomicCommit. Until
478 // this gets reworked (multiple requests?) screen cloning is not
479 // compatible with atomic.
480#endif
481 } else {
482 int ret = drmModePageFlip(fd,
483 destOutput.crtc_id,
484 fb->fb,
485 DRM_MODE_PAGE_FLIP_EVENT,
486 d.screen);
487 if (ret) {
488 qErrnoWarning("Could not queue DRM page flip for screen %s (clones screen %s)",
489 qPrintable(d.screen->name()),
490 qPrintable(name()));
491 d.cloneFlipPending = false;
492 }
493 }
494 }
495 }
496
497 if (device()->hasAtomicSupport()) {
498#if QT_CONFIG(drm_atomic)
499 if (!device()->threadLocalAtomicCommit(this)) {
500 return;
501 }
502#endif
503 }
504
505 gbmRelease.dismiss();
506}
507
508void QEglFSKmsGbmScreen::flipFinished()
509{
510 if (m_cloneSource) {
511 m_cloneSource->cloneDestFlipFinished(this);
512 return;
513 }
514
515 m_flipPending = false;
516 updateFlipStatus();
517}
518
519void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen)
520{
521 for (CloneDestination &d : m_cloneDests) {
522 if (d.screen == cloneDestScreen) {
523 d.cloneFlipPending = false;
524 break;
525 }
526 }
527 updateFlipStatus();
528}
529
530void QEglFSKmsGbmScreen::updateFlipStatus()
531{
532 // only for 'real' outputs that own the color buffer, i.e. that are not cloning another one
533 if (m_cloneSource)
534 return;
535
536 // proceed only if flips for both this and all others that clone this have finished
537 if (m_flipPending)
538 return;
539
540 for (const CloneDestination &d : std::as_const(m_cloneDests)) {
541 if (d.cloneFlipPending)
542 return;
543 }
544
545 if (m_gbm_bo_current) {
546 gbm_surface_release_buffer(m_gbm_surface,
547 m_gbm_bo_current);
548 }
549
550 m_gbm_bo_current = m_gbm_bo_next;
551 m_gbm_bo_next = nullptr;
552}
553
554QT_END_NAMESPACE
static uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat)
static uint32_t drmFormatToGbmFormat(uint32_t drmFormat)