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

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