| 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 | if (resource()) |
| 255 | m_clientBufferIntegration->removeBuffer(resource: resource()->handle); |
| 256 | deleteTextures(); |
| 257 | } |
| 258 | |
| 259 | void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource) |
| 260 | { |
| 261 | m_clientBufferIntegration->removeBuffer(resource: resource->handle); |
| 262 | wl_resource_destroy(resource->handle); |
| 263 | } |
| 264 | |
| 265 | void LinuxDmabufWlBuffer::deleteTextures() |
| 266 | { |
| 267 | QMutexLocker locker(&m_texturesLock); |
| 268 | |
| 269 | for (uint32_t i = 0; i < m_planesNumber; ++i) { |
| 270 | if (m_textures[i] != nullptr) { |
| 271 | QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(tex: m_textures[i], |
| 272 | ctx: m_texturesContext[i]); |
| 273 | m_textures[i] = nullptr; |
| 274 | m_texturesContext[i] = nullptr; |
| 275 | QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]); |
| 276 | m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection(); |
| 277 | } |
| 278 | if (m_eglImages[i] != EGL_NO_IMAGE_KHR) { |
| 279 | m_clientBufferIntegration->deleteImage(image: m_eglImages[i]); |
| 280 | m_eglImages[i] = EGL_NO_IMAGE_KHR; |
| 281 | } |
| 282 | if (m_planes[i].fd != -1) |
| 283 | close(fd: m_planes[i].fd); |
| 284 | m_planes[i].fd = -1; |
| 285 | } |
| 286 | m_planesNumber = 0; |
| 287 | } |
| 288 | |
| 289 | void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image) |
| 290 | { |
| 291 | Q_ASSERT(plane < m_planesNumber); |
| 292 | Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR); |
| 293 | m_eglImages[plane] = image; |
| 294 | } |
| 295 | |
| 296 | void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture) |
| 297 | { |
| 298 | QMutexLocker locker(&m_texturesLock); |
| 299 | |
| 300 | Q_ASSERT(plane < m_planesNumber); |
| 301 | Q_ASSERT(m_textures.at(plane) == nullptr); |
| 302 | Q_ASSERT(QOpenGLContext::currentContext()); |
| 303 | m_textures[plane] = texture; |
| 304 | m_texturesContext[plane] = QOpenGLContext::currentContext(); |
| 305 | |
| 306 | m_texturesAboutToBeDestroyedConnection[plane] = |
| 307 | QObject::connect(sender: m_texturesContext[plane], signal: &QOpenGLContext::aboutToBeDestroyed, |
| 308 | context: m_texturesContext[plane], slot: [this, plane]() { |
| 309 | |
| 310 | QMutexLocker locker(&this->m_texturesLock); |
| 311 | |
| 312 | // See above lock - there is a chance that this has already been removed from m_textures[plane]! |
| 313 | // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed! |
| 314 | if (this->m_textures[plane] == nullptr) |
| 315 | return; |
| 316 | |
| 317 | delete this->m_textures[plane]; |
| 318 | |
| 319 | qCDebug(qLcWaylandCompositorHardwareIntegration) |
| 320 | << Q_FUNC_INFO |
| 321 | << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!" |
| 322 | << "Pointer (now dead) was:" << (void*)(this->m_textures[plane]) |
| 323 | << " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]); |
| 324 | |
| 325 | this->m_textures[plane] = nullptr; |
| 326 | this->m_texturesContext[plane] = nullptr; |
| 327 | |
| 328 | QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]); |
| 329 | this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection(); |
| 330 | |
| 331 | }, type: Qt::DirectConnection); |
| 332 | } |
| 333 | |
| 334 | void LinuxDmabufWlBuffer::buffer_destroy_resource(Resource *resource) |
| 335 | { |
| 336 | // Nothing to do here, since lifetime is managed through BufferManager's destroy listener callback |
| 337 | Q_UNUSED(resource); |
| 338 | } |
| 339 | |
| 340 | QT_END_NAMESPACE |
| 341 | |