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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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