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
linuxdmabuf.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "linuxdmabuf.h"
6
7#include <QtCore/QStandardPaths>
8
9#include <QtWaylandCompositor/QWaylandCompositor>
10#include <QtWaylandCompositor/private/qwltextureorphanage_p.h>
11
12#include <drm_fourcc.h>
13#include <drm_mode.h>
14#include <unistd.h>
15#include <sys/stat.h>
16
17#include <fcntl.h>
18#include <sys/mman.h>
19
20#ifdef Q_OS_LINUX
21# include <sys/syscall.h>
22// from linux/memfd.h:
23# ifndef MFD_CLOEXEC
24# define MFD_CLOEXEC 0x0001U
25# endif
26# ifndef MFD_ALLOW_SEALING
27# define MFD_ALLOW_SEALING 0x0002U
28# endif
29// from bits/fcntl-linux.h
30# ifndef F_ADD_SEALS
31# define F_ADD_SEALS 1033
32# endif
33# ifndef F_SEAL_SEAL
34# define F_SEAL_SEAL 0x0001
35# endif
36# ifndef F_SEAL_SHRINK
37# define F_SEAL_SHRINK 0x0002
38# endif
39#endif
40
41QT_BEGIN_NAMESPACE
42
43LinuxDmabuf::LinuxDmabuf(int version,
44 wl_display *display,
45 LinuxDmabufClientBufferIntegration *clientBufferIntegration)
46 : zwp_linux_dmabuf_v1(display, version)
47 , m_clientBufferIntegration(clientBufferIntegration)
48{
49}
50
51void LinuxDmabuf::setDrmDevice(const char *drmDevice)
52{
53 m_drmDevice = drmDevice;
54}
55
56void LinuxDmabuf::setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers)
57{
58 Q_ASSERT(resourceMap().isEmpty());
59 m_modifiers = modifiers;
60}
61
63{
64 if (resource->version() >= ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION)
65 return;
66
67 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
68 auto format = it.key();
69 auto modifiers = it.value();
70 // send DRM_FORMAT_MOD_INVALID when no modifiers are supported for a format
71 if (modifiers.isEmpty())
72 modifiers << DRM_FORMAT_MOD_INVALID;
73 for (const auto &modifier : std::as_const(modifiers)) {
74 if (resource->version() >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
75 const uint32_t modifier_lo = modifier & 0xFFFFFFFF;
76 const uint32_t modifier_hi = modifier >> 32;
77 send_modifier(resource->handle, format, modifier_hi, modifier_lo);
78 } else if (modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_INVALID) {
79 send_format(resource->handle, format);
80 }
81 }
82 }
83}
84
85void LinuxDmabuf::zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id)
86{
87 wl_resource *r = wl_resource_create(resource->client(), &zwp_linux_buffer_params_v1_interface,
88 wl_resource_get_version(resource->handle), params_id);
89 new LinuxDmabufParams(m_clientBufferIntegration, r); // deleted by the client, or when it disconnects
90}
91
92void LinuxDmabuf::zwp_linux_dmabuf_v1_get_default_feedback(Resource *resource, uint32_t id)
93{
94 if (resource->version() < ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION)
95 return;
96
97 wl_resource *r = wl_resource_create(resource->client(),
98 &zwp_linux_dmabuf_feedback_v1_interface,
99 wl_resource_get_version(resource->handle),
100 id);
101 // Deleted by client
102 new LinuxDmabufFeedback(m_modifiers, m_drmDevice, r);
103}
104
105void LinuxDmabuf::zwp_linux_dmabuf_v1_get_surface_feedback(Resource *resource, uint32_t id, struct ::wl_resource *surface)
106{
107 if (resource->version() < ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION)
108 return;
109
110 Q_UNUSED(surface);
111 wl_resource *r = wl_resource_create(resource->client(),
112 &zwp_linux_dmabuf_feedback_v1_interface,
113 wl_resource_get_version(resource->handle),
114 id);
115 // Deleted by client
116 new LinuxDmabufFeedback(m_modifiers, m_drmDevice, r);
117}
118
119LinuxDmabufFeedback::LinuxDmabufFeedback(QHash<uint32_t, QList<uint64_t>> modifiers,
120 const char *drmDevice, wl_resource *res)
121 : zwp_linux_dmabuf_feedback_v1(res)
122 , m_modifiers(modifiers)
123 , m_drmDevice(drmDevice)
124{
125 sendFeedback(resource());
126}
127
129{
130 if (m_data)
131 munmap(m_data, m_size);
132}
133
135{
136 wl_resource_destroy(resource->handle);
137}
138
140{
141 Q_UNUSED(resource);
142 delete this;
143}
144
146{
147 sendFeedback(resource);
148}
149
150QByteArray LinuxDmabufFeedback::sendFormatTable(Resource *)
151{
152 QList<std::pair<uint32_t, uint64_t> > formatModifierPairs;
153 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
154 uint32_t format = it.key();
155 QList<uint64_t> modifiers = it.value();
156
157 if (!modifiers.isEmpty()) {
158 for (uint64_t modifier : modifiers)
159 formatModifierPairs.append(std::make_pair(format, modifier));
160 } else {
161 formatModifierPairs.append(std::make_pair(format, DRM_FORMAT_MOD_INVALID));
162 }
163 }
164
165 if (formatModifierPairs.isEmpty()) {
166 qCWarning(qLcWaylandCompositorHardwareIntegration) << "LinuxDmabufFeedback: No formats";
167 return QByteArray{};
168 }
169
170 int fd = -1;
171#ifdef SYS_memfd_create
172 fd = syscall(SYS_memfd_create, "wayland-dmabuf", MFD_CLOEXEC | MFD_ALLOW_SEALING);
173 if (fd >= 0)
174 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
175#endif
176
177 std::unique_ptr<QFile> filePointer;
178 if (fd == -1) {
179 auto tmpFile =
180 std::make_unique<QTemporaryFile>(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) +
181 QLatin1String("/wayland-dmabuf-XXXXXX"));
182 if (tmpFile->open())
183 filePointer = std::move(tmpFile);
184 } else {
185 auto file = std::make_unique<QFile>();
186 if (file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle))
187 filePointer = std::move(file);
188 }
189
190 m_size = formatModifierPairs.size() * 16;
191 if (!filePointer || !filePointer->resize(m_size)) {
192 qCWarning(qLcWaylandCompositorHardwareIntegration)
193 << "LinuxDmabufFeedback: failed: " << filePointer->errorString();
194 return QByteArray{};
195 }
196
197 fd = filePointer->handle();
198
199 if (m_data)
200 munmap(m_data, m_size);
201 m_data = (uchar *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
202
203 QByteArray indicesArray;
204 indicesArray.resize(formatModifierPairs.size() * 2); // Each index is 2 bytes
205 char *iptr = indicesArray.data();
206
207 // Copy data into array with native endianness
208 for (quint16 i = 0; i < quint16(formatModifierPairs.size()); ++i) {
209 memcpy(iptr, &i, 2);
210 iptr += 2;
211
212 const auto &formatModifierPair = formatModifierPairs.at(i);
213 uint32_t format = formatModifierPair.first;
214 uint64_t modifiers = formatModifierPair.second;
215 memcpy(m_data, &format, 4);
216 m_data += 4;
217
218 m_data += 4; // Padding
219
220 memcpy(m_data, &modifiers, 8);
221 m_data += 8;
222 }
223
224 send_format_table(fd, m_size);
225
226 // Returns indices for convenience
227 return indicesArray;
228}
229
230void LinuxDmabufFeedback::sendFeedback(Resource *resource)
231{
232 if (!m_drmDevice)
233 return;
234
235 QByteArray indices = sendFormatTable(resource);
236
237 // Query main device by getting the device number of the drm device,
238 // granted its still there.
239 struct stat drmDeviceStat;
240 if (stat(m_drmDevice, &drmDeviceStat)) {
241 qCWarning(qLcWaylandCompositorHardwareIntegration)
242 << "Failed to access DRM device in linux-dmabuf-feedback";
243 return;
244 }
245
246 dev_t mainDevice = drmDeviceStat.st_rdev;
247 QByteArray mainDeviceArray;
248 mainDeviceArray.setRawData(reinterpret_cast<const char *>(&mainDevice), sizeof(dev_t));
249 send_main_device(mainDeviceArray);
250
251 // At least one tranche required, we just send one with all formats
252 send_tranche_target_device(mainDeviceArray);
253 send_tranche_flags(0);
254 send_tranche_formats(indices);
255 send_tranche_done();
256
257 send_done();
258}
259
260LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
262 , m_clientBufferIntegration(clientBufferIntegration)
263{
264}
265
267{
268 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
269 if (it.value().fd != -1)
270 close(it.value().fd);
271 it.value().fd = -1;
272 }
273}
274
275bool LinuxDmabufParams::handleCreateParams(Resource *resource, int width, int height, uint format, uint flags)
276{
277 if (m_used) {
278 wl_resource_post_error(resource->handle,
279 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
280 "Params already used");
281 return false;
282 }
283
284 if (width <= 0 || height <= 0) {
285 wl_resource_post_error(resource->handle,
286 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
287 "Invalid dimensions in create request");
288 return false;
289 }
290
291 if (m_planes.isEmpty()) {
292 wl_resource_post_error(resource->handle,
293 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
294 "Cannot create a buffer with no planes");
295 return false;
296 }
297
298 // check for holes in plane sequence
299 auto planeIds = m_planes.keys();
300 std::sort(planeIds.begin(), planeIds.end());
301 for (int i = 0; i < planeIds.size(); ++i) {
302 if (uint(i) != planeIds[i]) {
303 wl_resource_post_error(resource->handle,
304 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
305 "No dmabuf parameters provided for plane %i", i);
306 return false;
307 }
308 }
309
310 // check for overflows
311 for (auto it = m_planes.constBegin(); it != m_planes.constEnd(); ++it) {
312 const auto planeId = it.key();
313 const auto plane = it.value();
314 if (static_cast<int64_t>(plane.offset) + plane.stride > UINT32_MAX) {
315 wl_resource_post_error(resource->handle,
316 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
317 "Size overflow for plane %i",
318 planeId);
319 return false;
320 }
321 if (planeId == 0 && static_cast<int64_t>(plane.offset) + plane.stride * static_cast<int64_t>(height) > UINT32_MAX) {
322 wl_resource_post_error(resource->handle,
323 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
324 "Size overflow for plane %i",
325 planeId);
326 return false;
327 }
328
329 // do not report an error as it might be caused by the kernel not supporting seeking on dmabuf
330 off_t size = lseek(plane.fd, 0, SEEK_END);
331 if (size == -1) {
332 qCDebug(qLcWaylandCompositorHardwareIntegration) << "Seeking is not supported";
333 continue;
334 }
335
336 if (static_cast<int64_t>(plane.offset) >= size) {
337 wl_resource_post_error(resource->handle,
338 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
339 "Invalid offset %i for plane %i",
340 plane.offset, planeId);
341 return false;
342 }
343
344 if (static_cast<int64_t>(plane.offset) + static_cast<int64_t>(plane.stride) > size) {
345 wl_resource_post_error(resource->handle,
346 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
347 "Invalid stride %i for plane %i",
348 plane.stride, planeId);
349 return false;
350 }
351
352 // only valid for first plane as other planes might be sub-sampled
353 if (planeId == 0 && plane.offset + static_cast<int64_t>(plane.stride) * height > size) {
354 wl_resource_post_error(resource->handle,
355 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
356 "Invalid buffer stride or height for plane %i", planeId);
357 return false;
358 }
359 }
360
361 m_size = QSize(width, height);
362 m_drmFormat = format;
363 m_flags = flags;
364 m_used = true;
365
366 return true;
367}
368
370{
371 wl_resource_destroy(resource->handle);
372}
373
375{
376 Q_UNUSED(resource);
377 delete this;
378}
379
380void LinuxDmabufParams::zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo)
381{
382 const uint64_t modifiers = (static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo;
383 if (plane_idx >= LinuxDmabufWlBuffer::MaxDmabufPlanes) {
384 wl_resource_post_error(resource->handle,
385 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
386 "Plane index %i is out of bounds", plane_idx);
387 }
388
389 if (m_planes.contains(plane_idx)) {
390 wl_resource_post_error(resource->handle,
391 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
392 "Plane already set");
393 }
394
395 Plane plane;
396 plane.fd = fd;
397 plane.modifiers = modifiers;
398 plane.offset = offset;
399 plane.stride = stride;
400 m_planes.insert(plane_idx, plane);
401}
402
403void LinuxDmabufParams::zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags)
404{
405 if (!handleCreateParams(resource, width, height, format, flags))
406 return;
407
408 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration);
409 buffer->m_size = m_size;
410 buffer->m_flags = m_flags;
411 buffer->m_drmFormat = m_drmFormat;
412 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
413 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
414 buffer->m_planes[it.key()] = it.value();
415 it.value().fd = -1; // ownership is moved
416 }
417
418 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
419 send_failed(resource->handle);
420 } else {
421 send_created(resource->handle, buffer->resource()->handle);
422 }
423}
424
425void LinuxDmabufParams::zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags)
426{
427 if (!handleCreateParams(resource, width, height, format, flags))
428 return;
429
430 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration, buffer_id);
431 buffer->m_size = m_size;
432 buffer->m_flags = m_flags;
433 buffer->m_drmFormat = m_drmFormat;
434 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
435 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
436 buffer->m_planes[it.key()] = it.value();
437 it.value().fd = -1; // ownership is moved
438 }
439
440 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
441 // for the 'create_immed' request, the implementation can decide
442 // how to handle the failure by an unknown cause; we decide
443 // to raise a fatal error at the client
444 wl_resource_post_error(resource->handle,
445 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
446 "Import of the provided DMA buffer failed");
447 }
448 // note: create signal shall not be sent for the 'create_immed' request
449}
450
451LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id)
452 : wl_buffer(client, id, 1 /*version*/)
453 , m_clientBufferIntegration(clientBufferIntegration)
454{
455}
456
458{
459 if (resource())
460 m_clientBufferIntegration->removeBuffer(resource()->handle);
461 deleteTextures();
462}
463
464void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource)
465{
466 m_clientBufferIntegration->removeBuffer(resource->handle);
467 wl_resource_destroy(resource->handle);
468}
469
470void LinuxDmabufWlBuffer::deleteTextures()
471{
472 QMutexLocker locker(&m_texturesLock);
473
474 for (uint32_t i = 0; i < m_planesNumber; ++i) {
475 if (m_textures[i] != nullptr) {
476 QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(m_textures[i],
477 m_texturesContext[i]);
478 m_textures[i] = nullptr;
479 m_texturesContext[i] = nullptr;
480 QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]);
481 m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
482 }
483 if (m_eglImages[i] != EGL_NO_IMAGE_KHR) {
484 m_clientBufferIntegration->deleteImage(m_eglImages[i]);
485 m_eglImages[i] = EGL_NO_IMAGE_KHR;
486 }
487 if (m_planes[i].fd != -1)
488 close(m_planes[i].fd);
489 m_planes[i].fd = -1;
490 }
491 m_planesNumber = 0;
492}
493
494void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image)
495{
496 Q_ASSERT(plane < m_planesNumber);
497 Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR);
498 m_eglImages[plane] = image;
499}
500
501void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture)
502{
503 QMutexLocker locker(&m_texturesLock);
504
505 Q_ASSERT(plane < m_planesNumber);
506 Q_ASSERT(m_textures.at(plane) == nullptr);
507 Q_ASSERT(QOpenGLContext::currentContext());
508 m_textures[plane] = texture;
509 m_texturesContext[plane] = QOpenGLContext::currentContext();
510
511 m_texturesAboutToBeDestroyedConnection[plane] =
512 QObject::connect(m_texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
513 m_texturesContext[plane], [this, plane]() {
514
515 QMutexLocker locker(&this->m_texturesLock);
516
517 // See above lock - there is a chance that this has already been removed from m_textures[plane]!
518 // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
519 if (this->m_textures[plane] == nullptr)
520 return;
521
522 delete this->m_textures[plane];
523
524 qCDebug(qLcWaylandCompositorHardwareIntegration)
525 << Q_FUNC_INFO
526 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
527 << "Pointer (now dead) was:" << (void*)(this->m_textures[plane])
528 << " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]);
529
530 this->m_textures[plane] = nullptr;
531 this->m_texturesContext[plane] = nullptr;
532
533 QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]);
534 this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
535
536 }, Qt::DirectConnection);
537}
538
540{
541 // In most cases this is redundant, but for instance if a buffer has been created,
542 // but not committed and the client disconnects, it is vital
543 m_clientBufferIntegration->removeBuffer(resource->handle);
544}
545
546QT_END_NAMESPACE
bool importBuffer(wl_resource *resource, LinuxDmabufWlBuffer *linuxDmabufBuffer)
void zwp_linux_dmabuf_feedback_v1_bind_resource(Resource *resource) override
void zwp_linux_dmabuf_feedback_v1_destroy_resource(Resource *resource) override
~LinuxDmabufFeedback() override
void zwp_linux_dmabuf_feedback_v1_destroy(Resource *resource) override
void zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags) override
~LinuxDmabufParams() override
LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
void zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags) override
void zwp_linux_buffer_params_v1_destroy(Resource *resource) override
void zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo) override
void zwp_linux_buffer_params_v1_destroy_resource(Resource *resource) override
void initTexture(uint32_t plane, QOpenGLTexture *texture)
void initImage(uint32_t plane, EGLImageKHR image)
void buffer_destroy(Resource *resource) override
~LinuxDmabufWlBuffer() override
LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id=0)
static const uint32_t MaxDmabufPlanes
void buffer_destroy_resource(Resource *resource) override
void zwp_linux_dmabuf_v1_get_surface_feedback(Resource *resource, uint32_t id, struct ::wl_resource *surface) override
void setDrmDevice(const char *drmDevice)
void zwp_linux_dmabuf_v1_bind_resource(Resource *resource) override
void setSupportedModifiers(const QHash< uint32_t, QList< uint64_t > > &modifiers)
void zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id) override
void zwp_linux_dmabuf_v1_get_default_feedback(Resource *resource, uint32_t id) override
#define DRM_FORMAT_MOD_LINEAR
Definition linuxdmabuf.h:34
#define DRM_FORMAT_MOD_INVALID
Definition linuxdmabuf.h:37
int fd
Definition linuxdmabuf.h:59
uint32_t stride
Definition linuxdmabuf.h:61
uint64_t modifiers
Definition linuxdmabuf.h:62
uint32_t offset
Definition linuxdmabuf.h:60