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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | LinuxDmabuf::LinuxDmabuf(wl_display *display, LinuxDmabufClientBufferIntegration *clientBufferIntegration) |
16 | : zwp_linux_dmabuf_v1(display, 3 /*version*/) |
17 | , m_clientBufferIntegration(clientBufferIntegration) |
18 | { |
19 | } |
20 | |
21 | void LinuxDmabuf::setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers) |
22 | { |
23 | Q_ASSERT(resourceMap().isEmpty()); |
24 | m_modifiers = modifiers; |
25 | } |
26 | |
27 | void 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 | |
47 | void 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 | |
54 | LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource) |
55 | : zwp_linux_buffer_params_v1(resource) |
56 | , m_clientBufferIntegration(clientBufferIntegration) |
57 | { |
58 | } |
59 | |
60 | LinuxDmabufParams::~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 | |
69 | bool 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 | |
163 | void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy(Resource *resource) |
164 | { |
165 | wl_resource_destroy(resource->handle); |
166 | } |
167 | |
168 | void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy_resource(Resource *resource) |
169 | { |
170 | Q_UNUSED(resource); |
171 | delete this; |
172 | } |
173 | |
174 | void 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 | |
197 | void 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 | |
219 | void 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 | |
245 | LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id) |
246 | : wl_buffer(client, id, 1 /*version*/) |
247 | , m_clientBufferIntegration(clientBufferIntegration) |
248 | { |
249 | } |
250 | |
251 | LinuxDmabufWlBuffer::~LinuxDmabufWlBuffer() |
252 | { |
253 | m_clientBufferIntegration->removeBuffer(resource: resource()->handle); |
254 | buffer_destroy(resource()); |
255 | } |
256 | |
257 | void 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 | |
282 | void 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 | |
289 | void 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 | |
327 | void LinuxDmabufWlBuffer::buffer_destroy_resource(Resource *resource) |
328 | { |
329 | Q_UNUSED(resource); |
330 | delete this; |
331 | } |
332 | |
333 | QT_END_NAMESPACE |
334 | |