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
55QT_BEGIN_NAMESPACE
56
57Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
58
59static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat)
60{
61 Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
62 return drmFormat;
63}
64
65static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat)
66{
67 Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
68 return gbmFormat;
69}
70
71void 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
83QEglFSKmsGbmScreen::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
113QEglFSKmsGbmScreen::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
124QEglFSKmsGbmScreen::~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
132QPlatformCursor *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
152gbm_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
195void QEglFSKmsGbmScreen::resetSurface()
196{
197 m_gbm_surface = nullptr;
198}
199
200void 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
220void 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: &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::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
286void 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
392void 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
403void 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
414void 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
434QT_END_NAMESPACE
435

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