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