1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> |
5 | ** Copyright (C) 2016 Pelagicore AG |
6 | ** Contact: https://www.qt.io/licensing/ |
7 | ** |
8 | ** This file is part of the plugins of the Qt Toolkit. |
9 | ** |
10 | ** $QT_BEGIN_LICENSE:LGPL$ |
11 | ** Commercial License Usage |
12 | ** Licensees holding valid commercial Qt licenses may use this file in |
13 | ** accordance with the commercial license agreement provided with the |
14 | ** Software or, alternatively, in accordance with the terms contained in |
15 | ** a written agreement between you and The Qt Company. For licensing terms |
16 | ** and conditions see https://www.qt.io/terms-conditions. For further |
17 | ** information use the contact form at https://www.qt.io/contact-us. |
18 | ** |
19 | ** GNU Lesser General Public License Usage |
20 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
21 | ** General Public License version 3 as published by the Free Software |
22 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
23 | ** packaging of this file. Please review the following information to |
24 | ** ensure the GNU Lesser General Public License version 3 requirements |
25 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
26 | ** |
27 | ** GNU General Public License Usage |
28 | ** Alternatively, this file may be used under the terms of the GNU |
29 | ** General Public License version 2.0 or (at your option) the GNU General |
30 | ** Public license version 3 or any later version approved by the KDE Free |
31 | ** Qt Foundation. The licenses are as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
33 | ** included in the packaging of this file. Please review the following |
34 | ** information to ensure the GNU General Public License requirements will |
35 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
36 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "qeglfskmsgbmscreen.h" |
43 | #include "qeglfskmsgbmdevice.h" |
44 | #include "qeglfskmsgbmcursor.h" |
45 | #include "qeglfsintegration_p.h" |
46 | |
47 | #include <QtCore/QLoggingCategory> |
48 | |
49 | #include <QtGui/private/qguiapplication_p.h> |
50 | #include <QtGui/private/qtguiglobal_p.h> |
51 | #include <QtFbSupport/private/qfbvthandler_p.h> |
52 | |
53 | #include <errno.h> |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) |
58 | |
59 | static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) |
60 | { |
61 | Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); |
62 | return drmFormat; |
63 | } |
64 | |
65 | static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat) |
66 | { |
67 | Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); |
68 | return gbmFormat; |
69 | } |
70 | |
71 | void QEglFSKmsGbmScreen::bufferDestroyedHandler(gbm_bo *bo, void *data) |
72 | { |
73 | FrameBuffer *fb = static_cast<FrameBuffer *>(data); |
74 | |
75 | if (fb->fb) { |
76 | gbm_device *device = gbm_bo_get_device(bo); |
77 | drmModeRmFB(fd: gbm_device_get_fd(gbm: device), bufferId: fb->fb); |
78 | } |
79 | |
80 | delete fb; |
81 | } |
82 | |
83 | QEglFSKmsGbmScreen::FrameBuffer *QEglFSKmsGbmScreen::framebufferForBufferObject(gbm_bo *bo) |
84 | { |
85 | { |
86 | FrameBuffer *fb = static_cast<FrameBuffer *>(gbm_bo_get_user_data(bo)); |
87 | if (fb) |
88 | return fb; |
89 | } |
90 | |
91 | uint32_t width = gbm_bo_get_width(bo); |
92 | uint32_t height = gbm_bo_get_height(bo); |
93 | uint32_t handles[4] = { gbm_bo_get_handle(bo).u32 }; |
94 | uint32_t strides[4] = { gbm_bo_get_stride(bo) }; |
95 | uint32_t offsets[4] = { 0 }; |
96 | uint32_t pixelFormat = gbmFormatToDrmFormat(gbmFormat: gbm_bo_get_format(bo)); |
97 | |
98 | QScopedPointer<FrameBuffer> fb(new FrameBuffer); |
99 | qCDebug(qLcEglfsKmsDebug, "Adding FB, size %ux%u, DRM format 0x%x" , width, height, pixelFormat); |
100 | |
101 | int ret = drmModeAddFB2(fd: device()->fd(), width, height, pixel_format: pixelFormat, |
102 | bo_handles: handles, pitches: strides, offsets, buf_id: &fb->fb, flags: 0); |
103 | |
104 | if (ret) { |
105 | qWarning(msg: "Failed to create KMS FB!" ); |
106 | return nullptr; |
107 | } |
108 | |
109 | gbm_bo_set_user_data(bo, data: fb.data(), destroy_user_data: bufferDestroyedHandler); |
110 | return fb.take(); |
111 | } |
112 | |
113 | QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QEglFSKmsDevice *device, const QKmsOutput &output, bool headless) |
114 | : QEglFSKmsScreen(device, output, headless) |
115 | , m_gbm_surface(nullptr) |
116 | , m_gbm_bo_current(nullptr) |
117 | , m_gbm_bo_next(nullptr) |
118 | , m_flipPending(false) |
119 | , m_cursor(nullptr) |
120 | , m_cloneSource(nullptr) |
121 | { |
122 | } |
123 | |
124 | QEglFSKmsGbmScreen::~QEglFSKmsGbmScreen() |
125 | { |
126 | const int remainingScreenCount = qGuiApp->screens().count(); |
127 | qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d" , remainingScreenCount); |
128 | if (!remainingScreenCount && !device()->screenConfig()->separateScreens()) |
129 | static_cast<QEglFSKmsGbmDevice *>(device())->destroyGlobalCursor(); |
130 | } |
131 | |
132 | QPlatformCursor *QEglFSKmsGbmScreen::cursor() const |
133 | { |
134 | QKmsScreenConfig *config = device()->screenConfig(); |
135 | if (config->headless()) |
136 | return nullptr; |
137 | if (config->hwCursor()) { |
138 | if (!config->separateScreens()) |
139 | return static_cast<QEglFSKmsGbmDevice *>(device())->globalCursor(); |
140 | |
141 | if (m_cursor.isNull()) { |
142 | QEglFSKmsGbmScreen *that = const_cast<QEglFSKmsGbmScreen *>(this); |
143 | that->m_cursor.reset(other: new QEglFSKmsGbmCursor(that)); |
144 | } |
145 | |
146 | return m_cursor.data(); |
147 | } else { |
148 | return QEglFSScreen::cursor(); |
149 | } |
150 | } |
151 | |
152 | gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig) |
153 | { |
154 | if (!m_gbm_surface) { |
155 | qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s" , qPrintable(name())); |
156 | |
157 | const auto gbmDevice = static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice(); |
158 | // If there was no format override given in the config file, |
159 | // query the native (here, gbm) format from the EGL config. |
160 | const bool queryFromEgl = !m_output.drm_format_requested_by_user; |
161 | if (queryFromEgl) { |
162 | EGLint native_format = -1; |
163 | EGLBoolean success = eglGetConfigAttrib(dpy: display(), config: eglConfig, EGL_NATIVE_VISUAL_ID, value: &native_format); |
164 | qCDebug(qLcEglfsKmsDebug) << "Got native format" << Qt::hex << native_format << Qt::dec |
165 | << "from eglGetConfigAttrib() with return code" << bool(success); |
166 | |
167 | if (success) { |
168 | m_gbm_surface = gbm_surface_create(gbm: gbmDevice, |
169 | width: rawGeometry().width(), |
170 | height: rawGeometry().height(), |
171 | format: native_format, |
172 | flags: GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
173 | if (m_gbm_surface) |
174 | m_output.drm_format = gbmFormatToDrmFormat(gbmFormat: native_format); |
175 | } |
176 | } |
177 | |
178 | // Fallback for older drivers, and when "format" is explicitly specified |
179 | // in the output config. (not guaranteed that the requested format works |
180 | // of course, but do what we are told to) |
181 | if (!m_gbm_surface) { |
182 | uint32_t gbmFormat = drmFormatToGbmFormat(drmFormat: m_output.drm_format); |
183 | if (queryFromEgl) |
184 | qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x" , gbmFormat); |
185 | m_gbm_surface = gbm_surface_create(gbm: gbmDevice, |
186 | width: rawGeometry().width(), |
187 | height: rawGeometry().height(), |
188 | format: gbmFormat, |
189 | flags: GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
190 | } |
191 | } |
192 | return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface() |
193 | } |
194 | |
195 | void QEglFSKmsGbmScreen::resetSurface() |
196 | { |
197 | m_gbm_surface = nullptr; |
198 | } |
199 | |
200 | void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones, |
201 | const QVector<QPlatformScreen *> &screensCloningThisScreen) |
202 | { |
203 | // clone destinations need to know the clone source |
204 | const bool clonesAnother = screenThisScreenClones != nullptr; |
205 | if (clonesAnother && !screensCloningThisScreen.isEmpty()) { |
206 | qWarning(msg: "QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time" , qPrintable(name())); |
207 | return; |
208 | } |
209 | if (clonesAnother) |
210 | m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones); |
211 | |
212 | // clone sources need to know their additional destinations |
213 | for (QPlatformScreen *s : screensCloningThisScreen) { |
214 | CloneDestination d; |
215 | d.screen = static_cast<QEglFSKmsGbmScreen *>(s); |
216 | m_cloneDests.append(t: d); |
217 | } |
218 | } |
219 | |
220 | void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb) |
221 | { |
222 | QKmsOutput &op(output()); |
223 | const int fd = device()->fd(); |
224 | |
225 | if (!op.mode_set) { |
226 | op.mode_set = true; |
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: ¤tMode->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 | |
265 | void QEglFSKmsGbmScreen::waitForFlip() |
266 | { |
267 | if (m_headless || m_cloneSource) |
268 | return; |
269 | |
270 | // Don't lock the mutex unless we actually need to |
271 | if (!m_gbm_bo_next) |
272 | return; |
273 | |
274 | m_flipMutex.lock(); |
275 | device()->eventReader()->startWaitFlip(key: this, mutex: &m_flipMutex, cond: &m_flipCond); |
276 | m_flipCond.wait(lockedMutex: &m_flipMutex); |
277 | m_flipMutex.unlock(); |
278 | |
279 | flipFinished(); |
280 | |
281 | #if QT_CONFIG(drm_atomic) |
282 | device()->threadLocalAtomicReset(); |
283 | #endif |
284 | } |
285 | |
286 | void QEglFSKmsGbmScreen::flip() |
287 | { |
288 | // For headless screen just return silently. It is not necessarily an error |
289 | // to end up here, so show no warnings. |
290 | if (m_headless) |
291 | return; |
292 | |
293 | if (m_cloneSource) { |
294 | qWarning(msg: "Screen %s clones another screen. swapBuffers() not allowed." , qPrintable(name())); |
295 | return; |
296 | } |
297 | |
298 | if (!m_gbm_surface) { |
299 | qWarning(msg: "Cannot sync before platform init!" ); |
300 | return; |
301 | } |
302 | |
303 | m_gbm_bo_next = gbm_surface_lock_front_buffer(surface: m_gbm_surface); |
304 | if (!m_gbm_bo_next) { |
305 | qWarning(msg: "Could not lock GBM surface front buffer!" ); |
306 | return; |
307 | } |
308 | |
309 | FrameBuffer *fb = framebufferForBufferObject(bo: m_gbm_bo_next); |
310 | ensureModeSet(fb: fb->fb); |
311 | |
312 | QKmsOutput &op(output()); |
313 | const int fd = device()->fd(); |
314 | m_flipPending = true; |
315 | |
316 | if (device()->hasAtomicSupport()) { |
317 | #if QT_CONFIG(drm_atomic) |
318 | drmModeAtomicReq *request = device()->threadLocalAtomicRequest(); |
319 | if (request) { |
320 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->framebufferPropertyId, value: fb->fb); |
321 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->crtcPropertyId, value: op.crtc_id); |
322 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->srcwidthPropertyId, |
323 | value: op.size.width() << 16); |
324 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->srcXPropertyId, value: 0); |
325 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->srcYPropertyId, value: 0); |
326 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->srcheightPropertyId, |
327 | value: op.size.height() << 16); |
328 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->crtcXPropertyId, value: 0); |
329 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->crtcYPropertyId, value: 0); |
330 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->crtcwidthPropertyId, |
331 | value: m_output.modes[m_output.mode].hdisplay); |
332 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->crtcheightPropertyId, |
333 | value: m_output.modes[m_output.mode].vdisplay); |
334 | |
335 | static int zpos = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_ZPOS" ); |
336 | if (zpos) |
337 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->zposPropertyId, value: zpos); |
338 | static uint blendOp = uint(qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_BLEND_OP" )); |
339 | if (blendOp) |
340 | drmModeAtomicAddProperty(req: request, object_id: op.eglfs_plane->id, property_id: op.eglfs_plane->blendOpPropertyId, value: blendOp); |
341 | } |
342 | #endif |
343 | } else { |
344 | int ret = drmModePageFlip(fd, |
345 | crtc_id: op.crtc_id, |
346 | fb_id: fb->fb, |
347 | DRM_MODE_PAGE_FLIP_EVENT, |
348 | user_data: this); |
349 | if (ret) { |
350 | qErrnoWarning(msg: "Could not queue DRM page flip on screen %s" , qPrintable(name())); |
351 | m_flipPending = false; |
352 | gbm_surface_release_buffer(surface: m_gbm_surface, bo: m_gbm_bo_next); |
353 | m_gbm_bo_next = nullptr; |
354 | return; |
355 | } |
356 | } |
357 | |
358 | for (CloneDestination &d : m_cloneDests) { |
359 | if (d.screen != this) { |
360 | d.screen->ensureModeSet(fb: fb->fb); |
361 | d.cloneFlipPending = true; |
362 | |
363 | if (device()->hasAtomicSupport()) { |
364 | #if QT_CONFIG(drm_atomic) |
365 | drmModeAtomicReq *request = device()->threadLocalAtomicRequest(); |
366 | if (request) { |
367 | drmModeAtomicAddProperty(req: request, object_id: d.screen->output().eglfs_plane->id, |
368 | property_id: d.screen->output().eglfs_plane->framebufferPropertyId, value: fb->fb); |
369 | drmModeAtomicAddProperty(req: request, object_id: d.screen->output().eglfs_plane->id, |
370 | property_id: d.screen->output().eglfs_plane->crtcPropertyId, value: op.crtc_id); |
371 | } |
372 | #endif |
373 | } else { |
374 | int ret = drmModePageFlip(fd, |
375 | crtc_id: d.screen->output().crtc_id, |
376 | fb_id: fb->fb, |
377 | DRM_MODE_PAGE_FLIP_EVENT, |
378 | user_data: d.screen); |
379 | if (ret) { |
380 | qErrnoWarning(msg: "Could not queue DRM page flip for clone screen %s" , qPrintable(name())); |
381 | d.cloneFlipPending = false; |
382 | } |
383 | } |
384 | } |
385 | } |
386 | |
387 | #if QT_CONFIG(drm_atomic) |
388 | device()->threadLocalAtomicCommit(user_data: this); |
389 | #endif |
390 | } |
391 | |
392 | void QEglFSKmsGbmScreen::flipFinished() |
393 | { |
394 | if (m_cloneSource) { |
395 | m_cloneSource->cloneDestFlipFinished(cloneDestScreen: this); |
396 | return; |
397 | } |
398 | |
399 | m_flipPending = false; |
400 | updateFlipStatus(); |
401 | } |
402 | |
403 | void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen) |
404 | { |
405 | for (CloneDestination &d : m_cloneDests) { |
406 | if (d.screen == cloneDestScreen) { |
407 | d.cloneFlipPending = false; |
408 | break; |
409 | } |
410 | } |
411 | updateFlipStatus(); |
412 | } |
413 | |
414 | void QEglFSKmsGbmScreen::updateFlipStatus() |
415 | { |
416 | Q_ASSERT(!m_cloneSource); |
417 | |
418 | if (m_flipPending) |
419 | return; |
420 | |
421 | for (const CloneDestination &d : qAsConst(t&: m_cloneDests)) { |
422 | if (d.cloneFlipPending) |
423 | return; |
424 | } |
425 | |
426 | if (m_gbm_bo_current) |
427 | gbm_surface_release_buffer(surface: m_gbm_surface, |
428 | bo: m_gbm_bo_current); |
429 | |
430 | m_gbm_bo_current = m_gbm_bo_next; |
431 | m_gbm_bo_next = nullptr; |
432 | } |
433 | |
434 | QT_END_NAMESPACE |
435 | |