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
6#include "qeglfskmsgbmscreen_p.h"
7#include "qeglfskmsgbmdevice_p.h"
8#include "qeglfskmsgbmcursor_p.h"
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::m_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(fd: gbm_device_get_fd(gbm: device), bufferId: 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(gbmFormat: 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(fd: device()->fd(), width, height, pixel_format: pixelFormat,
69 bo_handles: handles, pitches: strides, offsets, buf_id: &fb->fb, flags: 0);
70
71 if (ret) {
72 qWarning(msg: "Failed to create KMS FB!");
73 return nullptr;
74 }
75
76 auto res = fb.get();
77 gbm_bo_set_user_data(bo, data: fb.release(), destroy_user_data: 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. Remaining screens: %d", remainingScreenCount);
96 if (!remainingScreenCount && !device()->screenConfig()->separateScreens())
97 static_cast<QEglFSKmsGbmDevice *>(device())->destroyGlobalCursor();
98}
99
100QPlatformCursor *QEglFSKmsGbmScreen::cursor() const
101{
102 QKmsScreenConfig *config = device()->screenConfig();
103 if (config->headless())
104 return nullptr;
105 if (config->hwCursor()) {
106 if (!config->separateScreens())
107 return static_cast<QEglFSKmsGbmDevice *>(device())->globalCursor();
108
109 if (m_cursor.isNull()) {
110 QEglFSKmsGbmScreen *that = const_cast<QEglFSKmsGbmScreen *>(this);
111 that->m_cursor.reset(other: new QEglFSKmsGbmCursor(that));
112 }
113
114 return m_cursor.data();
115 } else {
116 return QEglFSScreen::cursor();
117 }
118}
119
120gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
121{
122 if (!m_gbm_surface) {
123 qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s", qPrintable(name()));
124
125 const auto gbmDevice = static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice();
126 // If there was no format override given in the config file,
127 // query the native (here, gbm) format from the EGL config.
128 const bool queryFromEgl = !m_output.drm_format_requested_by_user;
129 if (queryFromEgl) {
130 EGLint native_format = -1;
131 EGLBoolean success = eglGetConfigAttrib(dpy: display(), config: eglConfig, EGL_NATIVE_VISUAL_ID, value: &native_format);
132 qCDebug(qLcEglfsKmsDebug) << "Got native format" << Qt::hex << native_format << Qt::dec
133 << "from eglGetConfigAttrib() with return code" << bool(success);
134
135 if (success) {
136 m_gbm_surface = gbm_surface_create(gbm: gbmDevice,
137 width: rawGeometry().width(),
138 height: rawGeometry().height(),
139 format: native_format,
140 flags: gbmFlags());
141 if (m_gbm_surface)
142 m_output.drm_format = gbmFormatToDrmFormat(gbmFormat: native_format);
143 }
144 }
145
146 const uint32_t gbmFormat = drmFormatToGbmFormat(drmFormat: m_output.drm_format);
147
148 // Fallback for older drivers, and when "format" is explicitly specified
149 // in the output config. (not guaranteed that the requested format works
150 // of course, but do what we are told to)
151 if (!m_gbm_surface) {
152 if (queryFromEgl)
153 qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x", gbmFormat);
154 m_gbm_surface = gbm_surface_create(gbm: gbmDevice,
155 width: rawGeometry().width(),
156 height: rawGeometry().height(),
157 format: gbmFormat,
158 flags: gbmFlags());
159 }
160
161#ifndef Q_OS_VXWORKS
162 // Fallback for some drivers, its required to request with modifiers
163 if (!m_gbm_surface) {
164 uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
165
166 m_gbm_surface = gbm_surface_create_with_modifiers(gbm: gbmDevice,
167 width: rawGeometry().width(),
168 height: rawGeometry().height(),
169 format: gbmFormat,
170 modifiers: &modifier, count: 1);
171 }
172#endif
173 // Fail here, as it would fail with the next usage of the GBM surface, which is very unexpected
174 if (!m_gbm_surface)
175 qFatal(msg: "Could not create GBM surface!");
176 }
177 return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface()
178}
179
180void QEglFSKmsGbmScreen::resetSurface()
181{
182 m_flipPending = false; // not necessarily true but enough to keep bo_next
183 m_gbm_bo_current = nullptr;
184 m_gbm_surface = nullptr;
185
186 // Leave m_gbm_bo_next untouched. waitForFlip() should
187 // still do its work, when called. Otherwise we end up
188 // in device-is-busy errors if there is a new QWindow
189 // created afterwards. (QTBUG-122663)
190
191 // If not using atomic, will need a new drmModeSetCrtc if a new window
192 // gets created later on (and so there's a new fb).
193 if (!device()->hasAtomicSupport())
194 needsNewModeSetForNextFb = true;
195}
196
197void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones,
198 const QList<QPlatformScreen *> &screensCloningThisScreen)
199{
200 // clone destinations need to know the clone source
201 const bool clonesAnother = screenThisScreenClones != nullptr;
202 if (clonesAnother && !screensCloningThisScreen.isEmpty()) {
203 qWarning(msg: "QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time", qPrintable(name()));
204 return;
205 }
206 if (clonesAnother) {
207 m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones);
208 qCDebug(qLcEglfsKmsDebug, "Screen %s clones %s", qPrintable(name()), qPrintable(m_cloneSource->name()));
209 }
210
211 // clone sources need to know their additional destinations
212 for (QPlatformScreen *s : screensCloningThisScreen) {
213 CloneDestination d;
214 d.screen = static_cast<QEglFSKmsGbmScreen *>(s);
215 m_cloneDests.append(t: d);
216 }
217}
218
219void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
220{
221 QKmsOutput &op(output());
222 const int fd = device()->fd();
223
224 if (!op.mode_set || needsNewModeSetForNextFb) {
225 op.mode_set = true;
226 needsNewModeSetForNextFb = false;
227
228 bool doModeSet = true;
229 drmModeCrtcPtr currentMode = drmModeGetCrtc(fd, crtcId: op.crtc_id);
230 const bool alreadySet = currentMode && currentMode->buffer_id == fb && !memcmp(s1: &currentMode->mode, s2: &op.modes[op.mode], n: sizeof(drmModeModeInfo));
231 if (currentMode)
232 drmModeFreeCrtc(ptr: currentMode);
233 if (alreadySet)
234 doModeSet = false;
235
236 if (doModeSet) {
237 qCDebug(qLcEglfsKmsDebug, "Setting mode for screen %s", qPrintable(name()));
238
239 if (device()->hasAtomicSupport()) {
240#if QT_CONFIG(drm_atomic)
241 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
242 if (request) {
243 drmModeAtomicAddProperty(req: request, object_id: op.connector_id, property_id: op.crtcIdPropertyId, value: op.crtc_id);
244 drmModeAtomicAddProperty(req: request, object_id: op.crtc_id, property_id: op.modeIdPropertyId, value: op.mode_blob_id);
245 drmModeAtomicAddProperty(req: request, object_id: op.crtc_id, property_id: op.activePropertyId, value: 1);
246 }
247#endif
248 } else {
249 int ret = drmModeSetCrtc(fd,
250 crtcId: op.crtc_id,
251 bufferId: fb,
252 x: 0, y: 0,
253 connectors: &op.connector_id, count: 1,
254 mode: &op.modes[op.mode]);
255
256 if (ret == 0)
257 setPowerState(PowerStateOn);
258 else
259 qErrnoWarning(errno, msg: "Could not set DRM mode for screen %s", qPrintable(name()));
260 }
261 }
262 }
263}
264
265void QEglFSKmsGbmScreen::nonThreadedPageFlipHandler(int fd,
266 unsigned int sequence,
267 unsigned int tv_sec,
268 unsigned int tv_usec,
269 void *user_data)
270{
271 // note that with cloning involved this callback is called also for screens that clone another one
272 Q_UNUSED(fd);
273 QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data);
274 screen->flipFinished();
275 screen->pageFlipped(sequence, tv_sec, tv_usec);
276}
277
278void QEglFSKmsGbmScreen::waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen)
279{
280 m_flipMutex.lock();
281 QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
282 dev->eventReader()->startWaitFlip(key: screen, mutex: &m_flipMutex, cond: &m_flipCond);
283 m_flipCond.wait(lockedMutex: &m_flipMutex);
284 m_flipMutex.unlock();
285 screen->flipFinished();
286}
287
288void QEglFSKmsGbmScreen::waitForFlip()
289{
290 if (m_headless || m_cloneSource)
291 return;
292
293 // Don't lock the mutex unless we actually need to
294 if (!m_gbm_bo_next)
295 return;
296
297 QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
298 if (dev->usesEventReader()) {
299 waitForFlipWithEventReader(screen: this);
300 // Now, unlike on the other code path, we need to ensure the
301 // flips have completed for the screens that just scan out
302 // this one's content, because the eventReader's wait is
303 // per-output.
304 for (CloneDestination &d : m_cloneDests) {
305 if (d.screen != this)
306 waitForFlipWithEventReader(screen: d.screen);
307 }
308 } else {
309 QMutexLocker lock(&m_nonThreadedFlipMutex);
310 while (m_gbm_bo_next) {
311 drmEventContext drmEvent;
312 memset(s: &drmEvent, c: 0, n: sizeof(drmEvent));
313 drmEvent.version = 2;
314 drmEvent.vblank_handler = nullptr;
315 drmEvent.page_flip_handler = nonThreadedPageFlipHandler;
316 drmHandleEvent(fd: device()->fd(), evctx: &drmEvent);
317 }
318 }
319
320#if QT_CONFIG(drm_atomic)
321 device()->threadLocalAtomicReset();
322#endif
323}
324
325#if QT_CONFIG(drm_atomic)
326static void addAtomicFlip(drmModeAtomicReq *request, const QKmsOutput &output, uint32_t fb)
327{
328 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
329 property_id: output.eglfs_plane->framebufferPropertyId, value: fb);
330
331 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
332 property_id: output.eglfs_plane->crtcPropertyId, value: output.crtc_id);
333
334 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
335 property_id: output.eglfs_plane->srcwidthPropertyId, value: output.size.width() << 16);
336
337 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
338 property_id: output.eglfs_plane->srcXPropertyId, value: 0);
339
340 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
341 property_id: output.eglfs_plane->srcYPropertyId, value: 0);
342
343 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
344 property_id: output.eglfs_plane->srcheightPropertyId, value: output.size.height() << 16);
345
346 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
347 property_id: output.eglfs_plane->crtcXPropertyId, value: 0);
348
349 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
350 property_id: output.eglfs_plane->crtcYPropertyId, value: 0);
351
352 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
353 property_id: output.eglfs_plane->crtcwidthPropertyId, value: output.modes[output.mode].hdisplay);
354
355 drmModeAtomicAddProperty(req: request, object_id: output.eglfs_plane->id,
356 property_id: output.eglfs_plane->crtcheightPropertyId, value: output.modes[output.mode].vdisplay);
357}
358#endif
359
360void QEglFSKmsGbmScreen::flip()
361{
362 // For headless screen just return silently. It is not necessarily an error
363 // to end up here, so show no warnings.
364 if (m_headless)
365 return;
366
367 if (m_cloneSource) {
368 qWarning(msg: "Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name()));
369 return;
370 }
371
372 if (!m_gbm_surface) {
373 qWarning(msg: "Cannot sync before platform init!");
374 return;
375 }
376
377 m_gbm_bo_next = gbm_surface_lock_front_buffer(surface: m_gbm_surface);
378 if (!m_gbm_bo_next) {
379 qWarning(msg: "Could not lock GBM surface front buffer for screen %s", qPrintable(name()));
380 return;
381 }
382
383 auto gbmRelease = qScopeGuard(f: [this]{
384 m_flipPending = false;
385 gbm_surface_release_buffer(surface: m_gbm_surface, bo: m_gbm_bo_next);
386 m_gbm_bo_next = nullptr;
387 });
388
389 FrameBuffer *fb = framebufferForBufferObject(bo: m_gbm_bo_next);
390 if (!fb) {
391 qWarning(msg: "FrameBuffer not available. Cannot flip");
392 return;
393 }
394 ensureModeSet(fb: fb->fb);
395
396 const QKmsOutput &thisOutput(output());
397 const int fd = device()->fd();
398 m_flipPending = true;
399
400 if (device()->hasAtomicSupport()) {
401#if QT_CONFIG(drm_atomic)
402 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
403 if (request) {
404 addAtomicFlip(request, output: thisOutput, fb: fb->fb);
405 static int zpos = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_ZPOS");
406 if (zpos) {
407 drmModeAtomicAddProperty(req: request, object_id: thisOutput.eglfs_plane->id,
408 property_id: thisOutput.eglfs_plane->zposPropertyId, value: zpos);
409 }
410 static uint blendOp = uint(qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_BLEND_OP"));
411 if (blendOp) {
412 drmModeAtomicAddProperty(req: request, object_id: thisOutput.eglfs_plane->id,
413 property_id: thisOutput.eglfs_plane->blendOpPropertyId, value: blendOp);
414 }
415 }
416#endif
417 } else {
418 int ret = drmModePageFlip(fd,
419 crtc_id: thisOutput.crtc_id,
420 fb_id: fb->fb,
421 DRM_MODE_PAGE_FLIP_EVENT,
422 user_data: this);
423 if (ret) {
424 qErrnoWarning(msg: "Could not queue DRM page flip on screen %s", qPrintable(name()));
425 return;
426 }
427 }
428
429 for (CloneDestination &d : m_cloneDests) {
430 if (d.screen != this) {
431 d.screen->ensureModeSet(fb: fb->fb);
432 d.cloneFlipPending = true;
433 const QKmsOutput &destOutput(d.screen->output());
434
435 if (device()->hasAtomicSupport()) {
436#if QT_CONFIG(drm_atomic)
437 drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
438 if (request)
439 addAtomicFlip(request, output: destOutput, fb: fb->fb);
440
441 // ### This path is broken. On the other branch we can easily
442 // pass in d.screen as the user_data for drmModePageFlip, but
443 // using one atomic request breaks down here since we get events
444 // with the same user_data passed to drmModeAtomicCommit. Until
445 // this gets reworked (multiple requests?) screen cloning is not
446 // compatible with atomic.
447#endif
448 } else {
449 int ret = drmModePageFlip(fd,
450 crtc_id: destOutput.crtc_id,
451 fb_id: fb->fb,
452 DRM_MODE_PAGE_FLIP_EVENT,
453 user_data: d.screen);
454 if (ret) {
455 qErrnoWarning(msg: "Could not queue DRM page flip for screen %s (clones screen %s)",
456 qPrintable(d.screen->name()),
457 qPrintable(name()));
458 d.cloneFlipPending = false;
459 }
460 }
461 }
462 }
463
464 if (device()->hasAtomicSupport()) {
465#if QT_CONFIG(drm_atomic)
466 if (!device()->threadLocalAtomicCommit(user_data: this)) {
467 return;
468 }
469#endif
470 }
471
472 gbmRelease.dismiss();
473}
474
475void QEglFSKmsGbmScreen::flipFinished()
476{
477 if (m_cloneSource) {
478 m_cloneSource->cloneDestFlipFinished(cloneDestScreen: this);
479 return;
480 }
481
482 m_flipPending = false;
483 updateFlipStatus();
484}
485
486void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen)
487{
488 for (CloneDestination &d : m_cloneDests) {
489 if (d.screen == cloneDestScreen) {
490 d.cloneFlipPending = false;
491 break;
492 }
493 }
494 updateFlipStatus();
495}
496
497void QEglFSKmsGbmScreen::updateFlipStatus()
498{
499 // only for 'real' outputs that own the color buffer, i.e. that are not cloning another one
500 if (m_cloneSource)
501 return;
502
503 // proceed only if flips for both this and all others that clone this have finished
504 if (m_flipPending)
505 return;
506
507 for (const CloneDestination &d : std::as_const(t&: m_cloneDests)) {
508 if (d.cloneFlipPending)
509 return;
510 }
511
512 if (m_gbm_bo_current) {
513 gbm_surface_release_buffer(surface: m_gbm_surface,
514 bo: m_gbm_bo_current);
515 }
516
517 m_gbm_bo_current = m_gbm_bo_next;
518 m_gbm_bo_next = nullptr;
519}
520
521QT_END_NAMESPACE
522

source code of qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp