1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the plugins of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | // Experimental DRM dumb buffer backend. |
41 | // |
42 | // TODO: |
43 | // Multiscreen: QWindow-QScreen(-output) association. Needs some reorg (device cannot be owned by screen) |
44 | // Find card via devicediscovery like in eglfs_kms. |
45 | // Mode restore like QEglFSKmsInterruptHandler. |
46 | // grabWindow |
47 | |
48 | #include "qlinuxfbdrmscreen.h" |
49 | #include <QLoggingCategory> |
50 | #include <QGuiApplication> |
51 | #include <QPainter> |
52 | #include <QtFbSupport/private/qfbcursor_p.h> |
53 | #include <QtFbSupport/private/qfbwindow_p.h> |
54 | #include <QtKmsSupport/private/qkmsdevice_p.h> |
55 | #include <QtCore/private/qcore_unix_p.h> |
56 | #include <sys/mman.h> |
57 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | Q_LOGGING_CATEGORY(qLcFbDrm, "qt.qpa.fb" ) |
61 | |
62 | static const int BUFFER_COUNT = 2; |
63 | |
64 | class QLinuxFbDevice : public QKmsDevice |
65 | { |
66 | public: |
67 | struct Framebuffer { |
68 | Framebuffer() : handle(0), pitch(0), size(0), fb(0), p(MAP_FAILED) { } |
69 | uint32_t handle; |
70 | uint32_t pitch; |
71 | uint64_t size; |
72 | uint32_t fb; |
73 | void *p; |
74 | QImage wrapper; |
75 | }; |
76 | |
77 | struct Output { |
78 | Output() : backFb(0), flipped(false) { } |
79 | QKmsOutput kmsOutput; |
80 | Framebuffer fb[BUFFER_COUNT]; |
81 | QRegion dirty[BUFFER_COUNT]; |
82 | int backFb; |
83 | bool flipped; |
84 | QSize currentRes() const { |
85 | const drmModeModeInfo &modeInfo(kmsOutput.modes[kmsOutput.mode]); |
86 | return QSize(modeInfo.hdisplay, modeInfo.vdisplay); |
87 | } |
88 | }; |
89 | |
90 | QLinuxFbDevice(QKmsScreenConfig *screenConfig); |
91 | |
92 | bool open() override; |
93 | void close() override; |
94 | |
95 | void createFramebuffers(); |
96 | void destroyFramebuffers(); |
97 | void setMode(); |
98 | |
99 | void swapBuffers(Output *output); |
100 | |
101 | int outputCount() const { return m_outputs.count(); } |
102 | Output *output(int idx) { return &m_outputs[idx]; } |
103 | |
104 | private: |
105 | void *nativeDisplay() const override; |
106 | QPlatformScreen *createScreen(const QKmsOutput &output) override; |
107 | void registerScreen(QPlatformScreen *screen, |
108 | bool isPrimary, |
109 | const QPoint &virtualPos, |
110 | const QList<QPlatformScreen *> &virtualSiblings) override; |
111 | |
112 | bool createFramebuffer(Output *output, int bufferIdx); |
113 | void destroyFramebuffer(Output *output, int bufferIdx); |
114 | |
115 | static void pageFlipHandler(int fd, unsigned int sequence, |
116 | unsigned int tv_sec, unsigned int tv_usec, void *user_data); |
117 | |
118 | QList<Output> m_outputs; |
119 | }; |
120 | |
121 | QLinuxFbDevice::QLinuxFbDevice(QKmsScreenConfig *screenConfig) |
122 | : QKmsDevice(screenConfig, QStringLiteral("/dev/dri/card0" )) |
123 | { |
124 | } |
125 | |
126 | bool QLinuxFbDevice::open() |
127 | { |
128 | int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC); |
129 | if (fd == -1) { |
130 | qErrnoWarning("Could not open DRM device %s" , qPrintable(devicePath())); |
131 | return false; |
132 | } |
133 | |
134 | uint64_t hasDumbBuf = 0; |
135 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &hasDumbBuf) == -1 || !hasDumbBuf) { |
136 | qWarning("Dumb buffers not supported" ); |
137 | qt_safe_close(fd); |
138 | return false; |
139 | } |
140 | |
141 | setFd(fd); |
142 | |
143 | qCDebug(qLcFbDrm, "DRM device %s opened" , qPrintable(devicePath())); |
144 | |
145 | return true; |
146 | } |
147 | |
148 | void QLinuxFbDevice::close() |
149 | { |
150 | for (Output &output : m_outputs) |
151 | output.kmsOutput.cleanup(this); // restore mode |
152 | |
153 | m_outputs.clear(); |
154 | |
155 | if (fd() != -1) { |
156 | qCDebug(qLcFbDrm, "Closing DRM device" ); |
157 | qt_safe_close(fd()); |
158 | setFd(-1); |
159 | } |
160 | } |
161 | |
162 | void *QLinuxFbDevice::nativeDisplay() const |
163 | { |
164 | Q_UNREACHABLE(); |
165 | return nullptr; |
166 | } |
167 | |
168 | QPlatformScreen *QLinuxFbDevice::createScreen(const QKmsOutput &output) |
169 | { |
170 | qCDebug(qLcFbDrm, "Got a new output: %s" , qPrintable(output.name)); |
171 | Output o; |
172 | o.kmsOutput = output; |
173 | m_outputs.append(o); |
174 | return nullptr; // no platformscreen, we are not a platform plugin |
175 | } |
176 | |
177 | void QLinuxFbDevice::registerScreen(QPlatformScreen *screen, |
178 | bool isPrimary, |
179 | const QPoint &virtualPos, |
180 | const QList<QPlatformScreen *> &virtualSiblings) |
181 | { |
182 | Q_UNUSED(screen); |
183 | Q_UNUSED(isPrimary); |
184 | Q_UNUSED(virtualPos); |
185 | Q_UNUSED(virtualSiblings); |
186 | Q_UNREACHABLE(); |
187 | } |
188 | |
189 | static uint32_t bppForDrmFormat(uint32_t drmFormat) |
190 | { |
191 | switch (drmFormat) { |
192 | case DRM_FORMAT_RGB565: |
193 | case DRM_FORMAT_BGR565: |
194 | return 16; |
195 | default: |
196 | return 32; |
197 | } |
198 | } |
199 | |
200 | static int depthForDrmFormat(uint32_t drmFormat) |
201 | { |
202 | switch (drmFormat) { |
203 | case DRM_FORMAT_RGB565: |
204 | case DRM_FORMAT_BGR565: |
205 | return 16; |
206 | case DRM_FORMAT_XRGB8888: |
207 | case DRM_FORMAT_XBGR8888: |
208 | return 24; |
209 | case DRM_FORMAT_XRGB2101010: |
210 | case DRM_FORMAT_XBGR2101010: |
211 | return 30; |
212 | default: |
213 | return 32; |
214 | } |
215 | } |
216 | |
217 | static QImage::Format formatForDrmFormat(uint32_t drmFormat) |
218 | { |
219 | switch (drmFormat) { |
220 | case DRM_FORMAT_XRGB8888: |
221 | case DRM_FORMAT_XBGR8888: |
222 | return QImage::Format_RGB32; |
223 | case DRM_FORMAT_ARGB8888: |
224 | case DRM_FORMAT_ABGR8888: |
225 | return QImage::Format_ARGB32; |
226 | case DRM_FORMAT_RGB565: |
227 | case DRM_FORMAT_BGR565: |
228 | return QImage::Format_RGB16; |
229 | case DRM_FORMAT_XRGB2101010: |
230 | case DRM_FORMAT_XBGR2101010: |
231 | return QImage::Format_RGB30; |
232 | case DRM_FORMAT_ARGB2101010: |
233 | case DRM_FORMAT_ABGR2101010: |
234 | return QImage::Format_A2RGB30_Premultiplied; |
235 | default: |
236 | return QImage::Format_ARGB32; |
237 | } |
238 | } |
239 | |
240 | bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx) |
241 | { |
242 | const QSize size = output->currentRes(); |
243 | const uint32_t w = size.width(); |
244 | const uint32_t h = size.height(); |
245 | const uint32_t bpp = bppForDrmFormat(output->kmsOutput.drm_format); |
246 | drm_mode_create_dumb creq = { |
247 | h, |
248 | w, |
249 | bpp, |
250 | 0, 0, 0, 0 |
251 | }; |
252 | if (drmIoctl(fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) { |
253 | qErrnoWarning(errno, "Failed to create dumb buffer" ); |
254 | return false; |
255 | } |
256 | |
257 | Framebuffer &fb(output->fb[bufferIdx]); |
258 | fb.handle = creq.handle; |
259 | fb.pitch = creq.pitch; |
260 | fb.size = creq.size; |
261 | qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d and bpp %u: handle %u, pitch %u, size %u" , |
262 | w, h, bpp, fb.handle, fb.pitch, (uint) fb.size); |
263 | |
264 | uint32_t handles[4] = { fb.handle }; |
265 | uint32_t strides[4] = { fb.pitch }; |
266 | uint32_t offsets[4] = { 0 }; |
267 | |
268 | if (drmModeAddFB2(fd(), w, h, output->kmsOutput.drm_format, |
269 | handles, strides, offsets, &fb.fb, 0) == -1) { |
270 | qErrnoWarning(errno, "Failed to add FB" ); |
271 | return false; |
272 | } |
273 | |
274 | drm_mode_map_dumb mreq = { |
275 | fb.handle, |
276 | 0, 0 |
277 | }; |
278 | if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mreq) == -1) { |
279 | qErrnoWarning(errno, "Failed to map dumb buffer" ); |
280 | return false; |
281 | } |
282 | fb.p = mmap(0, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset); |
283 | if (fb.p == MAP_FAILED) { |
284 | qErrnoWarning(errno, "Failed to mmap dumb buffer" ); |
285 | return false; |
286 | } |
287 | |
288 | qCDebug(qLcFbDrm, "FB is %u (DRM format 0x%x), mapped at %p" , fb.fb, output->kmsOutput.drm_format, fb.p); |
289 | memset(fb.p, 0, fb.size); |
290 | |
291 | fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, formatForDrmFormat(output->kmsOutput.drm_format)); |
292 | |
293 | return true; |
294 | } |
295 | |
296 | void QLinuxFbDevice::createFramebuffers() |
297 | { |
298 | for (Output &output : m_outputs) { |
299 | for (int i = 0; i < BUFFER_COUNT; ++i) { |
300 | if (!createFramebuffer(&output, i)) |
301 | return; |
302 | } |
303 | output.backFb = 0; |
304 | output.flipped = false; |
305 | } |
306 | } |
307 | |
308 | void QLinuxFbDevice::destroyFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx) |
309 | { |
310 | Framebuffer &fb(output->fb[bufferIdx]); |
311 | if (fb.p != MAP_FAILED) |
312 | munmap(fb.p, fb.size); |
313 | if (fb.fb) { |
314 | if (drmModeRmFB(fd(), fb.fb) == -1) |
315 | qErrnoWarning("Failed to remove fb" ); |
316 | } |
317 | if (fb.handle) { |
318 | drm_mode_destroy_dumb dreq = { fb.handle }; |
319 | if (drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &dreq) == -1) |
320 | qErrnoWarning(errno, "Failed to destroy dumb buffer %u" , fb.handle); |
321 | } |
322 | fb = Framebuffer(); |
323 | } |
324 | |
325 | void QLinuxFbDevice::destroyFramebuffers() |
326 | { |
327 | for (Output &output : m_outputs) { |
328 | for (int i = 0; i < BUFFER_COUNT; ++i) |
329 | destroyFramebuffer(&output, i); |
330 | } |
331 | } |
332 | |
333 | void QLinuxFbDevice::setMode() |
334 | { |
335 | for (Output &output : m_outputs) { |
336 | drmModeModeInfo &modeInfo(output.kmsOutput.modes[output.kmsOutput.mode]); |
337 | if (drmModeSetCrtc(fd(), output.kmsOutput.crtc_id, output.fb[0].fb, 0, 0, |
338 | &output.kmsOutput.connector_id, 1, &modeInfo) == -1) { |
339 | qErrnoWarning(errno, "Failed to set mode" ); |
340 | return; |
341 | } |
342 | |
343 | output.kmsOutput.mode_set = true; // have cleanup() to restore the mode |
344 | output.kmsOutput.setPowerState(this, QPlatformScreen::PowerStateOn); |
345 | } |
346 | } |
347 | |
348 | void QLinuxFbDevice::pageFlipHandler(int fd, unsigned int sequence, |
349 | unsigned int tv_sec, unsigned int tv_usec, |
350 | void *user_data) |
351 | { |
352 | Q_UNUSED(fd); |
353 | Q_UNUSED(sequence); |
354 | Q_UNUSED(tv_sec); |
355 | Q_UNUSED(tv_usec); |
356 | |
357 | Output *output = static_cast<Output *>(user_data); |
358 | output->backFb = (output->backFb + 1) % BUFFER_COUNT; |
359 | } |
360 | |
361 | void QLinuxFbDevice::swapBuffers(Output *output) |
362 | { |
363 | Framebuffer &fb(output->fb[output->backFb]); |
364 | if (drmModePageFlip(fd(), output->kmsOutput.crtc_id, fb.fb, DRM_MODE_PAGE_FLIP_EVENT, output) == -1) { |
365 | qErrnoWarning(errno, "Page flip failed" ); |
366 | return; |
367 | } |
368 | |
369 | const int fbIdx = output->backFb; |
370 | while (output->backFb == fbIdx) { |
371 | drmEventContext drmEvent; |
372 | memset(&drmEvent, 0, sizeof(drmEvent)); |
373 | drmEvent.version = 2; |
374 | drmEvent.vblank_handler = nullptr; |
375 | drmEvent.page_flip_handler = pageFlipHandler; |
376 | // Blocks until there is something to read on the drm fd |
377 | // and calls back pageFlipHandler once the flip completes. |
378 | drmHandleEvent(fd(), &drmEvent); |
379 | } |
380 | } |
381 | |
382 | QLinuxFbDrmScreen::QLinuxFbDrmScreen(const QStringList &args) |
383 | : m_screenConfig(nullptr), |
384 | m_device(nullptr) |
385 | { |
386 | Q_UNUSED(args); |
387 | } |
388 | |
389 | QLinuxFbDrmScreen::~QLinuxFbDrmScreen() |
390 | { |
391 | if (m_device) { |
392 | m_device->destroyFramebuffers(); |
393 | m_device->close(); |
394 | delete m_device; |
395 | } |
396 | delete m_screenConfig; |
397 | } |
398 | |
399 | bool QLinuxFbDrmScreen::initialize() |
400 | { |
401 | m_screenConfig = new QKmsScreenConfig; |
402 | m_screenConfig->loadConfig(); |
403 | m_device = new QLinuxFbDevice(m_screenConfig); |
404 | if (!m_device->open()) |
405 | return false; |
406 | |
407 | // Discover outputs. Calls back Device::createScreen(). |
408 | m_device->createScreens(); |
409 | // Now off to dumb buffer specifics. |
410 | m_device->createFramebuffers(); |
411 | // Do the modesetting. |
412 | m_device->setMode(); |
413 | |
414 | QLinuxFbDevice::Output *output(m_device->output(0)); |
415 | |
416 | mGeometry = QRect(QPoint(0, 0), output->currentRes()); |
417 | mDepth = depthForDrmFormat(output->kmsOutput.drm_format); |
418 | mFormat = formatForDrmFormat(output->kmsOutput.drm_format); |
419 | mPhysicalSize = output->kmsOutput.physical_size; |
420 | qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize << mDepth << mFormat; |
421 | |
422 | QFbScreen::initializeCompositor(); |
423 | |
424 | mCursor = new QFbCursor(this); |
425 | |
426 | return true; |
427 | } |
428 | |
429 | QRegion QLinuxFbDrmScreen::doRedraw() |
430 | { |
431 | const QRegion dirty = QFbScreen::doRedraw(); |
432 | if (dirty.isEmpty()) |
433 | return dirty; |
434 | |
435 | QLinuxFbDevice::Output *output(m_device->output(0)); |
436 | |
437 | for (int i = 0; i < BUFFER_COUNT; ++i) |
438 | output->dirty[i] += dirty; |
439 | |
440 | if (output->fb[output->backFb].wrapper.isNull()) |
441 | return dirty; |
442 | |
443 | QPainter pntr(&output->fb[output->backFb].wrapper); |
444 | // Image has alpha but no need for blending at this stage. |
445 | // Do not waste time with the default SourceOver. |
446 | pntr.setCompositionMode(QPainter::CompositionMode_Source); |
447 | for (const QRect &rect : qAsConst(output->dirty[output->backFb])) |
448 | pntr.drawImage(rect, mScreenImage, rect); |
449 | pntr.end(); |
450 | |
451 | output->dirty[output->backFb] = QRegion(); |
452 | |
453 | m_device->swapBuffers(output); |
454 | |
455 | return dirty; |
456 | } |
457 | |
458 | QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int height) const |
459 | { |
460 | Q_UNUSED(wid); |
461 | Q_UNUSED(x); |
462 | Q_UNUSED(y); |
463 | Q_UNUSED(width); |
464 | Q_UNUSED(height); |
465 | |
466 | return QPixmap(); |
467 | } |
468 | |
469 | QT_END_NAMESPACE |
470 | |