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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) |
22 | |
23 | QMutex QEglFSKmsGbmScreen::m_nonThreadedFlipMutex; |
24 | |
25 | static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) |
26 | { |
27 | Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); |
28 | return drmFormat; |
29 | } |
30 | |
31 | static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat) |
32 | { |
33 | Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); |
34 | return gbmFormat; |
35 | } |
36 | |
37 | void 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 | |
49 | QEglFSKmsGbmScreen::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 | |
80 | QEglFSKmsGbmScreen::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 | |
91 | QEglFSKmsGbmScreen::~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 | |
99 | QPlatformCursor *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 | |
119 | gbm_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 | |
162 | void 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 | |
170 | void 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 | |
192 | void 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: ¤tMode->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 | |
237 | void 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 | |
250 | void 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 | |
260 | void 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) |
298 | static 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 | |
332 | void 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 | |
447 | void 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 | |
458 | void 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 | |
469 | void 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 | |
493 | QT_END_NAMESPACE |
494 | |