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