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, m_clientBufferIntegration, 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, m_clientBufferIntegration, r);
117}
118
119LinuxDmabufFeedback::LinuxDmabufFeedback(QHash<uint32_t, QList<uint64_t>> modifiers,
120 const char *drmDevice,
121 LinuxDmabufClientBufferIntegration *clientBufferIntegration,
122 wl_resource *res)
123 : zwp_linux_dmabuf_feedback_v1(res)
124 , m_modifiers(modifiers)
125 , m_drmDevice(drmDevice)
126 , m_clientBufferIntegration(clientBufferIntegration)
127{
128 sendFeedback(resource());
129}
130
132{
133 if (m_data)
134 munmap(m_data, m_size);
135}
136
138{
139 wl_resource_destroy(resource->handle);
140}
141
143{
144 Q_UNUSED(resource);
145 delete this;
146}
147
149{
150 sendFeedback(resource);
151}
152
153QByteArray LinuxDmabufFeedback::sendFormatTable(Resource *)
154{
155 QList<std::pair<uint32_t, uint64_t> > formatModifierPairs;
156 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
157 uint32_t format = it.key();
158 QList<uint64_t> modifiers = it.value();
159
160 if (!modifiers.isEmpty()) {
161 for (uint64_t modifier : modifiers)
162 formatModifierPairs.append(std::make_pair(format, modifier));
163 } else {
164 formatModifierPairs.append(std::make_pair(format, DRM_FORMAT_MOD_INVALID));
165 }
166 }
167
168 if (formatModifierPairs.isEmpty()) {
169 qCWarning(qLcWaylandCompositorHardwareIntegration) << "LinuxDmabufFeedback: No formats";
170 return QByteArray{};
171 }
172
173 int fd = -1;
174#ifdef SYS_memfd_create
175 fd = syscall(SYS_memfd_create, "wayland-dmabuf", MFD_CLOEXEC | MFD_ALLOW_SEALING);
176 if (fd >= 0)
177 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
178#endif
179
180 std::unique_ptr<QFile> filePointer;
181 if (fd == -1) {
182 auto tmpFile =
183 std::make_unique<QTemporaryFile>(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) +
184 QLatin1String("/wayland-dmabuf-XXXXXX"));
185 if (tmpFile->open())
186 filePointer = std::move(tmpFile);
187 } else {
188 auto file = std::make_unique<QFile>();
189 if (file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle))
190 filePointer = std::move(file);
191 }
192
193 m_size = formatModifierPairs.size() * 16;
194 if (!filePointer || !filePointer->resize(m_size)) {
195 qCWarning(qLcWaylandCompositorHardwareIntegration)
196 << "LinuxDmabufFeedback: failed: " << filePointer->errorString();
197 return QByteArray{};
198 }
199
200 fd = filePointer->handle();
201
202 if (m_data)
203 munmap(m_data, m_size);
204 m_data = (uchar *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
205
206 QByteArray indicesArray;
207 indicesArray.resize(formatModifierPairs.size() * 2); // Each index is 2 bytes
208 char *iptr = indicesArray.data();
209
210 // Copy data into array with native endianness
211 for (quint16 i = 0; i < quint16(formatModifierPairs.size()); ++i) {
212 memcpy(iptr, &i, 2);
213 iptr += 2;
214
215 const auto &formatModifierPair = formatModifierPairs.at(i);
216 uint32_t format = formatModifierPair.first;
217 uint64_t modifiers = formatModifierPair.second;
218 memcpy(m_data, &format, 4);
219 m_data += 4;
220
221 m_data += 4; // Padding
222
223 memcpy(m_data, &modifiers, 8);
224 m_data += 8;
225 }
226
227 send_format_table(fd, m_size);
228
229 // Returns indices for convenience
230 return indicesArray;
231}
232
233void LinuxDmabufFeedback::sendFeedback(Resource *resource)
234{
235 if (!m_drmDevice)
236 return;
237
238 QByteArray indices = sendFormatTable(resource);
239
240 // Query main device by getting the device number of the drm device,
241 // granted its still there.
242 struct stat drmDeviceStat;
243 if (stat(m_drmDevice, &drmDeviceStat)) {
244 qCWarning(qLcWaylandCompositorHardwareIntegration)
245 << "Failed to access DRM device in linux-dmabuf-feedback";
246 return;
247 }
248
249 dev_t mainDevice = drmDeviceStat.st_rdev;
250 QByteArray mainDeviceArray;
251 mainDeviceArray.setRawData(reinterpret_cast<const char *>(&mainDevice), sizeof(dev_t));
252 send_main_device(mainDeviceArray);
253
254 // At least one tranche required, we just send one with all formats
255 send_tranche_target_device(mainDeviceArray);
256 send_tranche_flags(0);
257 send_tranche_formats(indices);
258 send_tranche_done();
259
260 send_done();
261}
262
263LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
265 , m_clientBufferIntegration(clientBufferIntegration)
266{
267}
268
270{
271 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
272 if (it.value().fd != -1)
273 close(it.value().fd);
274 it.value().fd = -1;
275 }
276}
277
278bool LinuxDmabufParams::handleCreateParams(Resource *resource, int width, int height, uint format, uint flags)
279{
280 if (m_used) {
281 wl_resource_post_error(resource->handle,
282 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
283 "Params already used");
284 return false;
285 }
286
287 if (width <= 0 || height <= 0) {
288 wl_resource_post_error(resource->handle,
289 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
290 "Invalid dimensions in create request");
291 return false;
292 }
293
294 if (m_planes.isEmpty()) {
295 wl_resource_post_error(resource->handle,
296 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
297 "Cannot create a buffer with no planes");
298 return false;
299 }
300
301 // check for holes in plane sequence
302 auto planeIds = m_planes.keys();
303 std::sort(planeIds.begin(), planeIds.end());
304 for (int i = 0; i < planeIds.size(); ++i) {
305 if (uint(i) != planeIds[i]) {
306 wl_resource_post_error(resource->handle,
307 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
308 "No dmabuf parameters provided for plane %i", i);
309 return false;
310 }
311 }
312
313 // check for overflows
314 for (auto it = m_planes.constBegin(); it != m_planes.constEnd(); ++it) {
315 const auto planeId = it.key();
316 const auto plane = it.value();
317 if (static_cast<int64_t>(plane.offset) + plane.stride > UINT32_MAX) {
318 wl_resource_post_error(resource->handle,
319 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
320 "Size overflow for plane %i",
321 planeId);
322 return false;
323 }
324 if (planeId == 0 && static_cast<int64_t>(plane.offset) + plane.stride * static_cast<int64_t>(height) > UINT32_MAX) {
325 wl_resource_post_error(resource->handle,
326 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
327 "Size overflow for plane %i",
328 planeId);
329 return false;
330 }
331
332 // do not report an error as it might be caused by the kernel not supporting seeking on dmabuf
333 off_t size = lseek(plane.fd, 0, SEEK_END);
334 if (size == -1) {
335 qCDebug(qLcWaylandCompositorHardwareIntegration) << "Seeking is not supported";
336 continue;
337 }
338
339 if (static_cast<int64_t>(plane.offset) >= size) {
340 wl_resource_post_error(resource->handle,
341 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
342 "Invalid offset %i for plane %i",
343 plane.offset, planeId);
344 return false;
345 }
346
347 if (static_cast<int64_t>(plane.offset) + static_cast<int64_t>(plane.stride) > size) {
348 wl_resource_post_error(resource->handle,
349 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
350 "Invalid stride %i for plane %i",
351 plane.stride, planeId);
352 return false;
353 }
354
355 // only valid for first plane as other planes might be sub-sampled
356 if (planeId == 0 && plane.offset + static_cast<int64_t>(plane.stride) * height > size) {
357 wl_resource_post_error(resource->handle,
358 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
359 "Invalid buffer stride or height for plane %i", planeId);
360 return false;
361 }
362 }
363
364 m_size = QSize(width, height);
365 m_drmFormat = format;
366 m_flags = flags;
367 m_used = true;
368
369 return true;
370}
371
373{
374 wl_resource_destroy(resource->handle);
375}
376
378{
379 Q_UNUSED(resource);
380 delete this;
381}
382
383void 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)
384{
385 const uint64_t modifiers = (static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo;
386 if (plane_idx >= LinuxDmabufWlBuffer::MaxDmabufPlanes) {
387 wl_resource_post_error(resource->handle,
388 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
389 "Plane index %i is out of bounds", plane_idx);
390 }
391
392 if (m_planes.contains(plane_idx)) {
393 wl_resource_post_error(resource->handle,
394 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
395 "Plane already set");
396 }
397
398 Plane plane;
399 plane.fd = fd;
400 plane.modifiers = modifiers;
401 plane.offset = offset;
402 plane.stride = stride;
403 m_planes.insert(plane_idx, plane);
404}
405
406void LinuxDmabufParams::zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags)
407{
408 if (!handleCreateParams(resource, width, height, format, flags))
409 return;
410
411 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration);
412 buffer->m_size = m_size;
413 buffer->m_flags = m_flags;
414 buffer->m_drmFormat = m_drmFormat;
415 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
416 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
417 buffer->m_planes[it.key()] = it.value();
418 it.value().fd = -1; // ownership is moved
419 }
420
421 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
422 send_failed(resource->handle);
423 } else {
424 send_created(resource->handle, buffer->resource()->handle);
425 }
426}
427
428void 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)
429{
430 if (!handleCreateParams(resource, width, height, format, flags))
431 return;
432
433 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration, buffer_id);
434 buffer->m_size = m_size;
435 buffer->m_flags = m_flags;
436 buffer->m_drmFormat = m_drmFormat;
437 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
438 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
439 buffer->m_planes[it.key()] = it.value();
440 it.value().fd = -1; // ownership is moved
441 }
442
443 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
444 // for the 'create_immed' request, the implementation can decide
445 // how to handle the failure by an unknown cause; we decide
446 // to raise a fatal error at the client
447 wl_resource_post_error(resource->handle,
448 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
449 "Import of the provided DMA buffer failed");
450 }
451 // note: create signal shall not be sent for the 'create_immed' request
452}
453
454LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id)
455 : wl_buffer(client, id, 1 /*version*/)
456 , m_clientBufferIntegration(clientBufferIntegration)
457{
458}
459
461{
462 if (resource())
463 m_clientBufferIntegration->removeBuffer(resource()->handle);
464 deleteTextures();
465}
466
467void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource)
468{
469 m_clientBufferIntegration->removeBuffer(resource->handle);
470 wl_resource_destroy(resource->handle);
471}
472
473void LinuxDmabufWlBuffer::deleteTextures()
474{
475 QMutexLocker locker(&m_texturesLock);
476
477 for (uint32_t i = 0; i < m_planesNumber; ++i) {
478 if (m_textures[i] != nullptr) {
479 QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(m_textures[i],
480 m_texturesContext[i]);
481 m_textures[i] = nullptr;
482 m_texturesContext[i] = nullptr;
483 QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]);
484 m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
485 }
486 if (m_eglImages[i] != EGL_NO_IMAGE_KHR) {
487 m_clientBufferIntegration->deleteImage(m_eglImages[i]);
488 m_eglImages[i] = EGL_NO_IMAGE_KHR;
489 }
490 if (m_planes[i].fd != -1)
491 close(m_planes[i].fd);
492 m_planes[i].fd = -1;
493 }
494 m_planesNumber = 0;
495}
496
497void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image)
498{
499 Q_ASSERT(plane < m_planesNumber);
500 Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR);
501 m_eglImages[plane] = image;
502}
503
504void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture)
505{
506 QMutexLocker locker(&m_texturesLock);
507
508 Q_ASSERT(plane < m_planesNumber);
509 Q_ASSERT(m_textures.at(plane) == nullptr);
510 Q_ASSERT(QOpenGLContext::currentContext());
511 m_textures[plane] = texture;
512 m_texturesContext[plane] = QOpenGLContext::currentContext();
513
514 m_texturesAboutToBeDestroyedConnection[plane] =
515 QObject::connect(m_texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
516 m_texturesContext[plane], [this, plane]() {
517
518 QMutexLocker locker(&this->m_texturesLock);
519
520 // See above lock - there is a chance that this has already been removed from m_textures[plane]!
521 // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
522 if (this->m_textures[plane] == nullptr)
523 return;
524
525 delete this->m_textures[plane];
526
527 qCDebug(qLcWaylandCompositorHardwareIntegration)
528 << Q_FUNC_INFO
529 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
530 << "Pointer (now dead) was:" << (void*)(this->m_textures[plane])
531 << " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]);
532
533 this->m_textures[plane] = nullptr;
534 this->m_texturesContext[plane] = nullptr;
535
536 QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]);
537 this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
538
539 }, Qt::DirectConnection);
540}
541
543{
544 // In most cases this is redundant, but for instance if a buffer has been created,
545 // but not committed and the client disconnects, it is vital
546 m_clientBufferIntegration->removeBuffer(resource->handle);
547}
548
549QT_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