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
4// Experimental DRM dumb buffer backend.
5//
6// TODO:
7// Multiscreen: QWindow-QScreen(-output) association. Needs some reorg (device cannot be owned by screen)
8// Find card via devicediscovery like in eglfs_kms.
9// Mode restore like QEglFSKmsInterruptHandler.
10// grabWindow
11// Qt-Security score:significant reason:default
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
90
92{
93 int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC);
94 if (fd == -1) {
95 qErrnoWarning("Could not open DRM device %s", qPrintable(devicePath()));
96 return false;
97 }
98
99 uint64_t hasDumbBuf = 0;
100 if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &hasDumbBuf) == -1 || !hasDumbBuf) {
101 qWarning("Dumb buffers not supported");
102 qt_safe_close(fd);
103 return false;
104 }
105
106 setFd(fd);
107
108 qCDebug(qLcFbDrm, "DRM device %s opened", qPrintable(devicePath()));
109
110 return true;
111}
112
114{
115 for (Output &output : m_outputs)
116 output.kmsOutput.cleanup(this); // restore mode
117
118 m_outputs.clear();
119
120 if (fd() != -1) {
121 qCDebug(qLcFbDrm, "Closing DRM device");
122 qt_safe_close(fd());
123 setFd(-1);
124 }
125}
126
128{
129 Q_UNREACHABLE_RETURN(nullptr);
130}
131
132QPlatformScreen *QLinuxFbDevice::createScreen(const QKmsOutput &output)
133{
134 qCDebug(qLcFbDrm, "Got a new output: %s", qPrintable(output.name));
135 Output o;
136 o.kmsOutput = output;
137 m_outputs.append(o);
138 return nullptr; // no platformscreen, we are not a platform plugin
139}
140
141void QLinuxFbDevice::registerScreen(QPlatformScreen *screen,
142 bool isPrimary,
143 const QPoint &virtualPos,
144 const QList<QPlatformScreen *> &virtualSiblings)
145{
146 Q_UNUSED(screen);
147 Q_UNUSED(isPrimary);
148 Q_UNUSED(virtualPos);
149 Q_UNUSED(virtualSiblings);
150 Q_UNREACHABLE();
151}
152
153static uint32_t bppForDrmFormat(uint32_t drmFormat)
154{
155 switch (drmFormat) {
156 case DRM_FORMAT_RGB565:
157 case DRM_FORMAT_BGR565:
158 return 16;
159 default:
160 return 32;
161 }
162}
163
164static int depthForDrmFormat(uint32_t drmFormat)
165{
166 switch (drmFormat) {
167 case DRM_FORMAT_RGB565:
168 case DRM_FORMAT_BGR565:
169 return 16;
170 case DRM_FORMAT_XRGB8888:
171 case DRM_FORMAT_XBGR8888:
172 return 24;
173 case DRM_FORMAT_XRGB2101010:
174 case DRM_FORMAT_XBGR2101010:
175 return 30;
176 default:
177 return 32;
178 }
179}
180
181static QImage::Format formatForDrmFormat(uint32_t drmFormat)
182{
183 switch (drmFormat) {
184 case DRM_FORMAT_XRGB8888:
185 case DRM_FORMAT_XBGR8888:
186 return QImage::Format_RGB32;
187 case DRM_FORMAT_ARGB8888:
188 case DRM_FORMAT_ABGR8888:
189 return QImage::Format_ARGB32;
190 case DRM_FORMAT_RGB565:
191 case DRM_FORMAT_BGR565:
192 return QImage::Format_RGB16;
193 case DRM_FORMAT_XRGB2101010:
194 case DRM_FORMAT_XBGR2101010:
195 return QImage::Format_RGB30;
196 case DRM_FORMAT_ARGB2101010:
197 case DRM_FORMAT_ABGR2101010:
198 return QImage::Format_A2RGB30_Premultiplied;
199 default:
200 return QImage::Format_ARGB32;
201 }
202}
203
204bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
205{
206 const QSize size = output->currentRes();
207 const uint32_t w = size.width();
208 const uint32_t h = size.height();
209 const uint32_t bpp = bppForDrmFormat(output->kmsOutput.drm_format);
210 drm_mode_create_dumb creq = {
211 h,
212 w,
213 bpp,
214 0, 0, 0, 0
215 };
216 if (drmIoctl(fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) {
217 qErrnoWarning(errno, "Failed to create dumb buffer");
218 return false;
219 }
220
221 Framebuffer &fb(output->fb[bufferIdx]);
222 fb.handle = creq.handle;
223 fb.pitch = creq.pitch;
224 fb.size = creq.size;
225 qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d and bpp %u: handle %u, pitch %u, size %u",
226 w, h, bpp, fb.handle, fb.pitch, (uint) fb.size);
227
228 uint32_t handles[4] = { fb.handle };
229 uint32_t strides[4] = { fb.pitch };
230 uint32_t offsets[4] = { 0 };
231
232 if (drmModeAddFB2(fd(), w, h, output->kmsOutput.drm_format,
233 handles, strides, offsets, &fb.fb, 0) == -1) {
234 qErrnoWarning(errno, "Failed to add FB");
235 return false;
236 }
237
238 drm_mode_map_dumb mreq = {
239 fb.handle,
240 0, 0
241 };
242 if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mreq) == -1) {
243 qErrnoWarning(errno, "Failed to map dumb buffer");
244 return false;
245 }
246 fb.p = mmap(nullptr, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset);
247 if (fb.p == MAP_FAILED) {
248 qErrnoWarning(errno, "Failed to mmap dumb buffer");
249 return false;
250 }
251
252 qCDebug(qLcFbDrm, "FB is %u (DRM format 0x%x), mapped at %p", fb.fb, output->kmsOutput.drm_format, fb.p);
253 memset(fb.p, 0, fb.size);
254
255 fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, formatForDrmFormat(output->kmsOutput.drm_format));
256
257 return true;
258}
259
261{
262 for (Output &output : m_outputs) {
263 for (int i = 0; i < BUFFER_COUNT; ++i) {
264 if (!createFramebuffer(&output, i))
265 return;
266 }
267 output.backFb = 0;
268 output.flipped = false;
269 }
270}
271
272void QLinuxFbDevice::destroyFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
273{
274 Framebuffer &fb(output->fb[bufferIdx]);
275 if (fb.p != MAP_FAILED)
276 munmap(fb.p, fb.size);
277 if (fb.fb) {
278 if (drmModeRmFB(fd(), fb.fb) == -1)
279 qErrnoWarning("Failed to remove fb");
280 }
281 if (fb.handle) {
282 drm_mode_destroy_dumb dreq = { fb.handle };
283 if (drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &dreq) == -1)
284 qErrnoWarning(errno, "Failed to destroy dumb buffer %u", fb.handle);
285 }
286 fb = Framebuffer();
287}
288
290{
291 for (Output &output : m_outputs) {
292 for (int i = 0; i < BUFFER_COUNT; ++i)
293 destroyFramebuffer(&output, i);
294 }
295}
296
298{
299 for (Output &output : m_outputs) {
300 drmModeModeInfo &modeInfo(output.kmsOutput.modes[output.kmsOutput.mode]);
301 if (drmModeSetCrtc(fd(), output.kmsOutput.crtc_id, output.fb[0].fb, 0, 0,
302 &output.kmsOutput.connector_id, 1, &modeInfo) == -1) {
303 qErrnoWarning(errno, "Failed to set mode");
304 return;
305 }
306
307 output.kmsOutput.mode_set = true; // have cleanup() to restore the mode
308 output.kmsOutput.setPowerState(this, QPlatformScreen::PowerStateOn);
309 }
310}
311
312void QLinuxFbDevice::pageFlipHandler(int fd, unsigned int sequence,
313 unsigned int tv_sec, unsigned int tv_usec,
314 void *user_data)
315{
316 Q_UNUSED(fd);
317 Q_UNUSED(sequence);
318 Q_UNUSED(tv_sec);
319 Q_UNUSED(tv_usec);
320
321 Output *output = static_cast<Output *>(user_data);
322 output->backFb = (output->backFb + 1) % BUFFER_COUNT;
323}
324
326{
327 Framebuffer &fb(output->fb[output->backFb]);
328 if (drmModePageFlip(fd(), output->kmsOutput.crtc_id, fb.fb, DRM_MODE_PAGE_FLIP_EVENT, output) == -1) {
329 qErrnoWarning(errno, "Page flip failed");
330 return;
331 }
332
333 const int fbIdx = output->backFb;
334 while (output->backFb == fbIdx) {
335 drmEventContext drmEvent;
336 memset(&drmEvent, 0, sizeof(drmEvent));
337 drmEvent.version = 2;
338 drmEvent.vblank_handler = nullptr;
339 drmEvent.page_flip_handler = pageFlipHandler;
340 // Blocks until there is something to read on the drm fd
341 // and calls back pageFlipHandler once the flip completes.
342 drmHandleEvent(fd(), &drmEvent);
343 }
344}
345
346QLinuxFbDrmScreen::QLinuxFbDrmScreen(const QStringList &args)
347 : m_screenConfig(nullptr),
348 m_device(nullptr)
349{
350 Q_UNUSED(args);
351}
352
354{
355 if (m_device) {
357 m_device->close();
358 delete m_device;
359 }
360 delete m_screenConfig;
361}
362
364{
365 m_screenConfig = new QKmsScreenConfig;
366 m_screenConfig->loadConfig();
367 m_device = new QLinuxFbDevice(m_screenConfig);
368 if (!m_device->open())
369 return false;
370
371 // Discover outputs. Calls back Device::createScreen().
372 m_device->createScreens();
373 // Now off to dumb buffer specifics.
375 // Do the modesetting.
376 m_device->setMode();
377
378 QLinuxFbDevice::Output *output(m_device->output(0));
379
380 mGeometry = QRect(QPoint(0, 0), output->currentRes());
381 mDepth = depthForDrmFormat(output->kmsOutput.drm_format);
382 mFormat = formatForDrmFormat(output->kmsOutput.drm_format);
383 mPhysicalSize = output->kmsOutput.physical_size;
384 qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize << mDepth << mFormat;
385
386 QFbScreen::initializeCompositor();
387
388 mCursor = new QFbCursor(this);
389
390 return true;
391}
392
394{
395 const QRegion dirty = QFbScreen::doRedraw();
396 if (dirty.isEmpty())
397 return dirty;
398
399 QLinuxFbDevice::Output *output(m_device->output(0));
400
401 for (int i = 0; i < BUFFER_COUNT; ++i)
402 output->dirty[i] += dirty;
403
404 if (output->fb[output->backFb].wrapper.isNull())
405 return dirty;
406
407 QPainter pntr(&output->fb[output->backFb].wrapper);
408 // Image has alpha but no need for blending at this stage.
409 // Do not waste time with the default SourceOver.
410 pntr.setCompositionMode(QPainter::CompositionMode_Source);
411 for (const QRect &rect : std::as_const(output->dirty[output->backFb]))
412 pntr.drawImage(rect, mScreenImage, rect);
413 pntr.end();
414
415 output->dirty[output->backFb] = QRegion();
416
417 m_device->swapBuffers(output);
418
419 return dirty;
420}
421
422QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int height) const
423{
424 Q_UNUSED(wid);
425 Q_UNUSED(x);
426 Q_UNUSED(y);
427 Q_UNUSED(width);
428 Q_UNUSED(height);
429
430 return QPixmap();
431}
432
433QT_END_NAMESPACE
434
435#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 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]