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