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"
5#include "linuxdmabufclientbufferintegration.h"
6
7#include <QtWaylandCompositor/QWaylandCompositor>
8
9#include <drm_fourcc.h>
10#include <drm_mode.h>
11#include <unistd.h>
12
13QT_BEGIN_NAMESPACE
14
15LinuxDmabuf::LinuxDmabuf(wl_display *display, LinuxDmabufClientBufferIntegration *clientBufferIntegration)
16 : zwp_linux_dmabuf_v1(display, 3 /*version*/)
17 , m_clientBufferIntegration(clientBufferIntegration)
18{
19}
20
21void LinuxDmabuf::setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers)
22{
23 Q_ASSERT(resourceMap().isEmpty());
24 m_modifiers = modifiers;
25}
26
27void LinuxDmabuf::zwp_linux_dmabuf_v1_bind_resource(Resource *resource)
28{
29 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
30 auto format = it.key();
31 auto modifiers = it.value();
32 // send DRM_FORMAT_MOD_INVALID when no modifiers are supported for a format
33 if (modifiers.isEmpty())
34 modifiers << DRM_FORMAT_MOD_INVALID;
35 for (const auto &modifier : std::as_const(t&: modifiers)) {
36 if (resource->version() >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
37 const uint32_t modifier_lo = modifier & 0xFFFFFFFF;
38 const uint32_t modifier_hi = modifier >> 32;
39 send_modifier(resource->handle, format, modifier_hi, modifier_lo);
40 } else if (modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_INVALID) {
41 send_format(resource->handle, format);
42 }
43 }
44 }
45}
46
47void LinuxDmabuf::zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id)
48{
49 wl_resource *r = wl_resource_create(resource->client(), &zwp_linux_buffer_params_v1_interface,
50 wl_resource_get_version(resource->handle), params_id);
51 new LinuxDmabufParams(m_clientBufferIntegration, r); // deleted by the client, or when it disconnects
52}
53
54LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
55 : zwp_linux_buffer_params_v1(resource)
56 , m_clientBufferIntegration(clientBufferIntegration)
57{
58}
59
60LinuxDmabufParams::~LinuxDmabufParams()
61{
62 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
63 if (it.value().fd != -1)
64 close(fd: it.value().fd);
65 it.value().fd = -1;
66 }
67}
68
69bool LinuxDmabufParams::handleCreateParams(Resource *resource, int width, int height, uint format, uint flags)
70{
71 if (m_used) {
72 wl_resource_post_error(resource->handle,
73 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
74 "Params already used");
75 return false;
76 }
77
78 if (width <= 0 || height <= 0) {
79 wl_resource_post_error(resource->handle,
80 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
81 "Invalid dimensions in create request");
82 return false;
83 }
84
85 if (m_planes.isEmpty()) {
86 wl_resource_post_error(resource->handle,
87 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
88 "Cannot create a buffer with no planes");
89 return false;
90 }
91
92 // check for holes in plane sequence
93 auto planeIds = m_planes.keys();
94 std::sort(first: planeIds.begin(), last: planeIds.end());
95 for (int i = 0; i < planeIds.size(); ++i) {
96 if (uint(i) != planeIds[i]) {
97 wl_resource_post_error(resource->handle,
98 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
99 "No dmabuf parameters provided for plane %i", i);
100 return false;
101 }
102 }
103
104 // check for overflows
105 for (auto it = m_planes.constBegin(); it != m_planes.constEnd(); ++it) {
106 const auto planeId = it.key();
107 const auto plane = it.value();
108 if (static_cast<int64_t>(plane.offset) + plane.stride > UINT32_MAX) {
109 wl_resource_post_error(resource->handle,
110 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
111 "Size overflow for plane %i",
112 planeId);
113 return false;
114 }
115 if (planeId == 0 && static_cast<int64_t>(plane.offset) + plane.stride * static_cast<int64_t>(height) > UINT32_MAX) {
116 wl_resource_post_error(resource->handle,
117 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
118 "Size overflow for plane %i",
119 planeId);
120 return false;
121 }
122
123 // do not report an error as it might be caused by the kernel not supporting seeking on dmabuf
124 off_t size = lseek(fd: plane.fd, offset: 0, SEEK_END);
125 if (size == -1) {
126 qCDebug(qLcWaylandCompositorHardwareIntegration) << "Seeking is not supported";
127 continue;
128 }
129
130 if (static_cast<int64_t>(plane.offset) >= size) {
131 wl_resource_post_error(resource->handle,
132 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
133 "Invalid offset %i for plane %i",
134 plane.offset, planeId);
135 return false;
136 }
137
138 if (static_cast<int64_t>(plane.offset) + static_cast<int64_t>(plane.stride) > size) {
139 wl_resource_post_error(resource->handle,
140 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
141 "Invalid stride %i for plane %i",
142 plane.stride, planeId);
143 return false;
144 }
145
146 // only valid for first plane as other planes might be sub-sampled
147 if (planeId == 0 && plane.offset + static_cast<int64_t>(plane.stride) * height > size) {
148 wl_resource_post_error(resource->handle,
149 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
150 "Invalid buffer stride or height for plane %i", planeId);
151 return false;
152 }
153 }
154
155 m_size = QSize(width, height);
156 m_drmFormat = format;
157 m_flags = flags;
158 m_used = true;
159
160 return true;
161}
162
163void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy(Resource *resource)
164{
165 wl_resource_destroy(resource->handle);
166}
167
168void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy_resource(Resource *resource)
169{
170 Q_UNUSED(resource);
171 delete this;
172}
173
174void 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)
175{
176 const uint64_t modifiers = (static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo;
177 if (plane_idx >= LinuxDmabufWlBuffer::MaxDmabufPlanes) {
178 wl_resource_post_error(resource->handle,
179 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
180 "Plane index %i is out of bounds", plane_idx);
181 }
182
183 if (m_planes.contains(key: plane_idx)) {
184 wl_resource_post_error(resource->handle,
185 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
186 "Plane already set");
187 }
188
189 Plane plane;
190 plane.fd = fd;
191 plane.modifiers = modifiers;
192 plane.offset = offset;
193 plane.stride = stride;
194 m_planes.insert(key: plane_idx, value: plane);
195}
196
197void LinuxDmabufParams::zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags)
198{
199 if (!handleCreateParams(resource, width, height, format, flags))
200 return;
201
202 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration);
203 buffer->m_size = m_size;
204 buffer->m_flags = m_flags;
205 buffer->m_drmFormat = m_drmFormat;
206 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
207 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
208 buffer->m_planes[it.key()] = it.value();
209 it.value().fd = -1; // ownership is moved
210 }
211
212 if (!m_clientBufferIntegration->importBuffer(resource: buffer->resource()->handle, linuxDmabufBuffer: buffer)) {
213 send_failed(resource->handle);
214 } else {
215 send_created(resource->handle, buffer->resource()->handle);
216 }
217}
218
219void 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)
220{
221 if (!handleCreateParams(resource, width, height, format, flags))
222 return;
223
224 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration, buffer_id);
225 buffer->m_size = m_size;
226 buffer->m_flags = m_flags;
227 buffer->m_drmFormat = m_drmFormat;
228 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
229 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
230 buffer->m_planes[it.key()] = it.value();
231 it.value().fd = -1; // ownership is moved
232 }
233
234 if (!m_clientBufferIntegration->importBuffer(resource: buffer->resource()->handle, linuxDmabufBuffer: buffer)) {
235 // for the 'create_immed' request, the implementation can decide
236 // how to handle the failure by an unknown cause; we decide
237 // to raise a fatal error at the client
238 wl_resource_post_error(resource->handle,
239 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
240 "Import of the provided DMA buffer failed");
241 }
242 // note: create signal shall not be sent for the 'create_immed' request
243}
244
245LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id)
246 : wl_buffer(client, id, 1 /*version*/)
247 , m_clientBufferIntegration(clientBufferIntegration)
248{
249}
250
251LinuxDmabufWlBuffer::~LinuxDmabufWlBuffer()
252{
253 m_clientBufferIntegration->removeBuffer(resource: resource()->handle);
254 buffer_destroy(resource());
255}
256
257void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource)
258{
259 Q_UNUSED(resource);
260
261 QMutexLocker locker(&m_texturesLock);
262
263 for (uint32_t i = 0; i < m_planesNumber; ++i) {
264 if (m_textures[i] != nullptr) {
265 m_clientBufferIntegration->deleteGLTextureWhenPossible(texture: m_textures[i], ctx: m_texturesContext[i]);
266 m_textures[i] = nullptr;
267 m_texturesContext[i] = nullptr;
268 QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]);
269 m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
270 }
271 if (m_eglImages[i] != EGL_NO_IMAGE_KHR) {
272 m_clientBufferIntegration->deleteImage(image: m_eglImages[i]);
273 m_eglImages[i] = EGL_NO_IMAGE_KHR;
274 }
275 if (m_planes[i].fd != -1)
276 close(fd: m_planes[i].fd);
277 m_planes[i].fd = -1;
278 }
279 m_planesNumber = 0;
280}
281
282void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image)
283{
284 Q_ASSERT(plane < m_planesNumber);
285 Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR);
286 m_eglImages[plane] = image;
287}
288
289void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture)
290{
291 QMutexLocker locker(&m_texturesLock);
292
293 Q_ASSERT(plane < m_planesNumber);
294 Q_ASSERT(m_textures.at(plane) == nullptr);
295 Q_ASSERT(QOpenGLContext::currentContext());
296 m_textures[plane] = texture;
297 m_texturesContext[plane] = QOpenGLContext::currentContext();
298
299 m_texturesAboutToBeDestroyedConnection[plane] =
300 QObject::connect(sender: m_texturesContext[plane], signal: &QOpenGLContext::aboutToBeDestroyed,
301 context: m_texturesContext[plane], slot: [this, plane]() {
302
303 QMutexLocker locker(&this->m_texturesLock);
304
305 // See above lock - there is a chance that this has already been removed from m_textures[plane]!
306 // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
307 if (this->m_textures[plane] == nullptr)
308 return;
309
310 delete this->m_textures[plane];
311
312 qCDebug(qLcWaylandCompositorHardwareIntegration)
313 << Q_FUNC_INFO
314 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
315 << "Pointer (now dead) was:" << (void*)(this->m_textures[plane])
316 << " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]);
317
318 this->m_textures[plane] = nullptr;
319 this->m_texturesContext[plane] = nullptr;
320
321 QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]);
322 this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
323
324 }, type: Qt::DirectConnection);
325}
326
327void LinuxDmabufWlBuffer::buffer_destroy_resource(Resource *resource)
328{
329 Q_UNUSED(resource);
330 delete this;
331}
332
333QT_END_NAMESPACE
334

source code of qtwayland/src/hardwareintegration/compositor/linux-dmabuf-unstable-v1/linuxdmabuf.cpp