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