File indexing completed on 2024-04-28 09:21:51
0001 /* 0002 Render a PipeWire stream into a QtQuick scene as a standard Item 0003 SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "pipewiresourceitem.h" 0009 #include "glhelpers.h" 0010 #include "logging.h" 0011 #include "pipewiresourcestream.h" 0012 0013 #include <QGuiApplication> 0014 #include <QOpenGLContext> 0015 #include <QOpenGLTexture> 0016 #include <QPainter> 0017 #include <QQuickWindow> 0018 #include <QRunnable> 0019 #include <QSGImageNode> 0020 #include <QSocketNotifier> 0021 #include <QThread> 0022 #include <qpa/qplatformnativeinterface.h> 0023 0024 #include <EGL/eglext.h> 0025 #include <fcntl.h> 0026 #include <libdrm/drm_fourcc.h> 0027 #include <unistd.h> 0028 0029 static void pwInit() 0030 { 0031 pw_init(nullptr, nullptr); 0032 } 0033 Q_COREAPP_STARTUP_FUNCTION(pwInit); 0034 0035 class PipeWireSourceItemPrivate 0036 { 0037 public: 0038 uint m_nodeId = 0; 0039 std::optional<uint> m_fd; 0040 std::function<QSGTexture *()> m_createNextTexture; 0041 QScopedPointer<PipeWireSourceStream> m_stream; 0042 QScopedPointer<QOpenGLTexture> m_texture; 0043 0044 EGLImage m_image = nullptr; 0045 bool m_needsRecreateTexture = false; 0046 bool m_allowDmaBuf = true; 0047 0048 struct { 0049 QImage texture; 0050 std::optional<QPoint> position; 0051 QPoint hotspot; 0052 bool dirty = false; 0053 } m_cursor; 0054 std::optional<QRegion> m_damage; 0055 }; 0056 0057 class DiscardEglPixmapRunnable : public QRunnable 0058 { 0059 public: 0060 DiscardEglPixmapRunnable(EGLImageKHR image, QOpenGLTexture *texture) 0061 : m_image(image) 0062 , m_texture(texture) 0063 { 0064 } 0065 0066 void run() override 0067 { 0068 if (m_image != EGL_NO_IMAGE_KHR) { 0069 eglDestroyImageKHR(eglGetCurrentDisplay(), m_image); 0070 } 0071 0072 delete m_texture; 0073 } 0074 0075 private: 0076 const EGLImageKHR m_image; 0077 QOpenGLTexture *m_texture; 0078 }; 0079 0080 PipeWireSourceItem::PipeWireSourceItem(QQuickItem *parent) 0081 : QQuickItem(parent) 0082 , d(new PipeWireSourceItemPrivate) 0083 { 0084 setFlag(ItemHasContents, true); 0085 0086 connect(this, &QQuickItem::visibleChanged, this, [this]() { 0087 setEnabled(isVisible()); 0088 if (d->m_stream) 0089 d->m_stream->setActive(isVisible()); 0090 }); 0091 } 0092 0093 PipeWireSourceItem::~PipeWireSourceItem() 0094 { 0095 if (d->m_fd) { 0096 close(*d->m_fd); 0097 } 0098 } 0099 0100 void PipeWireSourceItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) 0101 { 0102 switch (change) { 0103 case ItemVisibleHasChanged: 0104 setEnabled(isVisible()); 0105 if (d->m_stream) 0106 d->m_stream->setActive(isVisible() && data.boolValue && isComponentComplete()); 0107 break; 0108 case ItemSceneChange: 0109 d->m_needsRecreateTexture = true; 0110 releaseResources(); 0111 break; 0112 default: 0113 break; 0114 } 0115 0116 QQuickItem::itemChange(change, data); 0117 } 0118 0119 void PipeWireSourceItem::releaseResources() 0120 { 0121 if (window() && (d->m_image || d->m_texture)) { 0122 window()->scheduleRenderJob(new DiscardEglPixmapRunnable(d->m_image, d->m_texture.take()), QQuickWindow::NoStage); 0123 d->m_image = EGL_NO_IMAGE_KHR; 0124 } 0125 } 0126 0127 void PipeWireSourceItem::setFd(uint fd) 0128 { 0129 if (fd == d->m_fd) 0130 return; 0131 0132 if (d->m_fd) { 0133 close(*d->m_fd); 0134 } 0135 d->m_fd = fd; 0136 refresh(); 0137 Q_EMIT fdChanged(fd); 0138 } 0139 0140 void PipeWireSourceItem::resetFd() 0141 { 0142 if (!d->m_fd.has_value()) { 0143 return; 0144 } 0145 0146 setEnabled(false); 0147 close(*d->m_fd); 0148 d->m_fd.reset(); 0149 d->m_stream.reset(nullptr); 0150 d->m_createNextTexture = [] { 0151 return nullptr; 0152 }; 0153 Q_EMIT streamSizeChanged(); 0154 } 0155 0156 void PipeWireSourceItem::refresh() 0157 { 0158 setEnabled(false); 0159 0160 if (!isComponentComplete()) { 0161 return; 0162 } 0163 0164 if (d->m_nodeId == 0) { 0165 releaseResources(); 0166 d->m_stream.reset(nullptr); 0167 Q_EMIT streamSizeChanged(); 0168 0169 d->m_createNextTexture = [] { 0170 return nullptr; 0171 }; 0172 } else { 0173 d->m_stream.reset(new PipeWireSourceStream(this)); 0174 d->m_stream->setAllowDmaBuf(d->m_allowDmaBuf); 0175 Q_EMIT streamSizeChanged(); 0176 connect(d->m_stream.get(), &PipeWireSourceStream::streamParametersChanged, this, &PipeWireSourceItem::streamSizeChanged); 0177 connect(d->m_stream.get(), &PipeWireSourceStream::streamParametersChanged, this, &PipeWireSourceItem::usingDmaBufChanged); 0178 0179 d->m_stream->createStream(d->m_nodeId, d->m_fd.value_or(0)); 0180 if (!d->m_stream->error().isEmpty()) { 0181 d->m_stream.reset(nullptr); 0182 d->m_nodeId = 0; 0183 return; 0184 } 0185 d->m_stream->setActive(isVisible() && isComponentComplete()); 0186 0187 connect(d->m_stream.data(), &PipeWireSourceStream::frameReceived, this, &PipeWireSourceItem::processFrame); 0188 connect(d->m_stream.data(), &PipeWireSourceStream::stateChanged, this, &PipeWireSourceItem::stateChanged); 0189 } 0190 Q_EMIT stateChanged(); 0191 } 0192 0193 void PipeWireSourceItem::setNodeId(uint nodeId) 0194 { 0195 if (nodeId == d->m_nodeId) 0196 return; 0197 0198 d->m_nodeId = nodeId; 0199 refresh(); 0200 Q_EMIT nodeIdChanged(nodeId); 0201 } 0202 0203 class PipeWireRenderNode : public QSGNode 0204 { 0205 public: 0206 QSGImageNode *screenNode(QQuickWindow *window) 0207 { 0208 if (!m_screenNode) { 0209 m_screenNode = window->createImageNode(); 0210 appendChildNode(m_screenNode); 0211 } 0212 return m_screenNode; 0213 } 0214 QSGImageNode *cursorNode(QQuickWindow *window) 0215 { 0216 if (!m_cursorNode) { 0217 m_cursorNode = window->createImageNode(); 0218 appendChildNode(m_cursorNode); 0219 } 0220 return m_cursorNode; 0221 } 0222 0223 QSGImageNode *damageNode(QQuickWindow *window) 0224 { 0225 if (!m_damageNode) { 0226 m_damageNode = window->createImageNode(); 0227 appendChildNode(m_damageNode); 0228 } 0229 return m_damageNode; 0230 } 0231 0232 void discardCursor() 0233 { 0234 if (m_cursorNode) { 0235 removeChildNode(m_cursorNode); 0236 delete m_cursorNode; 0237 m_cursorNode = nullptr; 0238 } 0239 } 0240 0241 void discardDamage() 0242 { 0243 if (m_damageNode) { 0244 removeChildNode(m_damageNode); 0245 delete m_damageNode; 0246 m_damageNode = nullptr; 0247 } 0248 } 0249 0250 private: 0251 QSGImageNode *m_screenNode = nullptr; 0252 QSGImageNode *m_cursorNode = nullptr; 0253 QSGImageNode *m_damageNode = nullptr; 0254 }; 0255 0256 QSGNode *PipeWireSourceItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) 0257 { 0258 if (Q_UNLIKELY(!d->m_createNextTexture)) { 0259 return node; 0260 } 0261 0262 auto texture = d->m_createNextTexture(); 0263 if (!texture) { 0264 delete node; 0265 return nullptr; 0266 } 0267 0268 QSGImageNode *screenNode; 0269 auto pwNode = dynamic_cast<PipeWireRenderNode *>(node); 0270 if (!pwNode) { 0271 delete node; 0272 pwNode = new PipeWireRenderNode; 0273 screenNode = window()->createImageNode(); 0274 screenNode->setOwnsTexture(true); 0275 pwNode->appendChildNode(screenNode); 0276 } else { 0277 screenNode = static_cast<QSGImageNode *>(pwNode->childAtIndex(0)); 0278 } 0279 screenNode->setTexture(texture); 0280 0281 const auto br = boundingRect().toRect(); 0282 QRect rect({0, 0}, texture->textureSize().scaled(br.size(), Qt::KeepAspectRatio)); 0283 rect.moveCenter(br.center()); 0284 screenNode->setRect(rect); 0285 0286 if (!d->m_cursor.position.has_value() || d->m_cursor.texture.isNull()) { 0287 pwNode->discardCursor(); 0288 } else { 0289 QSGImageNode *cursorNode = pwNode->cursorNode(window()); 0290 if (d->m_cursor.dirty || !cursorNode->texture()) { 0291 cursorNode->setTexture(window()->createTextureFromImage(d->m_cursor.texture)); 0292 d->m_cursor.dirty = false; 0293 } 0294 const qreal scale = qreal(rect.width()) / texture->textureSize().width(); 0295 cursorNode->setRect(QRectF{rect.topLeft() + (d->m_cursor.position.value() * scale), d->m_cursor.texture.size() * scale}); 0296 Q_ASSERT(cursorNode->texture()); 0297 } 0298 0299 if (!d->m_damage || d->m_damage->isEmpty()) { 0300 pwNode->discardDamage(); 0301 } else { 0302 auto *damageNode = pwNode->damageNode(window()); 0303 QImage damageImage(texture->textureSize(), QImage::Format_RGBA64_Premultiplied); 0304 damageImage.fill(Qt::transparent); 0305 QPainter p(&damageImage); 0306 p.setBrush(Qt::red); 0307 for (auto rect : *d->m_damage) { 0308 p.drawRect(rect); 0309 } 0310 damageNode->setTexture(window()->createTextureFromImage(damageImage)); 0311 damageNode->setRect(rect); 0312 Q_ASSERT(damageNode->texture()); 0313 } 0314 return pwNode; 0315 } 0316 0317 QString PipeWireSourceItem::error() const 0318 { 0319 return d->m_stream->error(); 0320 } 0321 0322 void PipeWireSourceItem::processFrame(const PipeWireFrame &frame) 0323 { 0324 d->m_damage = frame.damage; 0325 0326 if (frame.cursor) { 0327 d->m_cursor.position = frame.cursor->position; 0328 d->m_cursor.hotspot = frame.cursor->hotspot; 0329 if (!frame.cursor->texture.isNull()) { 0330 d->m_cursor.dirty = true; 0331 d->m_cursor.texture = frame.cursor->texture; 0332 } 0333 } else { 0334 d->m_cursor.position = std::nullopt; 0335 d->m_cursor.hotspot = {}; 0336 } 0337 0338 if (frame.dmabuf) { 0339 updateTextureDmaBuf(*frame.dmabuf, frame.format); 0340 } else if (frame.image) { 0341 updateTextureImage(*frame.image); 0342 } 0343 0344 if (window() && window()->isVisible()) { 0345 update(); 0346 } 0347 } 0348 0349 void PipeWireSourceItem::updateTextureDmaBuf(const DmaBufAttributes &attribs, spa_video_format format) 0350 { 0351 if (!window()) { 0352 qCWarning(PIPEWIRE_LOGGING) << "Window not available" << this; 0353 return; 0354 } 0355 0356 const auto openglContext = static_cast<QOpenGLContext *>(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); 0357 if (!openglContext || !d->m_stream) { 0358 qCWarning(PIPEWIRE_LOGGING) << "need a window and a context" << window(); 0359 return; 0360 } 0361 0362 d->m_createNextTexture = [this, format, attribs]() -> QSGTexture * { 0363 const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay")); 0364 if (d->m_image) { 0365 eglDestroyImageKHR(display, d->m_image); 0366 } 0367 const auto size = d->m_stream->size(); 0368 d->m_image = GLHelpers::createImage(display, attribs, PipeWireSourceStream::spaVideoFormatToDrmFormat(format), size, nullptr); 0369 if (d->m_image == EGL_NO_IMAGE_KHR) { 0370 d->m_stream->renegotiateModifierFailed(format, attribs.modifier); 0371 return nullptr; 0372 } 0373 if (!d->m_texture) { 0374 d->m_texture.reset(new QOpenGLTexture(QOpenGLTexture::Target2D)); 0375 bool created = d->m_texture->create(); 0376 Q_ASSERT(created); 0377 } 0378 0379 GLHelpers::initDebugOutput(); 0380 d->m_texture->bind(); 0381 0382 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)d->m_image); 0383 0384 d->m_texture->setWrapMode(QOpenGLTexture::ClampToEdge); 0385 d->m_texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); 0386 d->m_texture->release(); 0387 d->m_texture->setSize(size.width(), size.height()); 0388 0389 int textureId = d->m_texture->textureId(); 0390 QQuickWindow::CreateTextureOption textureOption = 0391 format == SPA_VIDEO_FORMAT_ARGB || format == SPA_VIDEO_FORMAT_BGRA ? QQuickWindow::TextureHasAlphaChannel : QQuickWindow::TextureIsOpaque; 0392 setEnabled(true); 0393 return QNativeInterface::QSGOpenGLTexture::fromNative(textureId, window(), size, textureOption); 0394 }; 0395 } 0396 0397 void PipeWireSourceItem::updateTextureImage(const QImage &image) 0398 { 0399 if (!window()) { 0400 qCWarning(PIPEWIRE_LOGGING) << "pass"; 0401 return; 0402 } 0403 0404 d->m_createNextTexture = [this, image] { 0405 setEnabled(true); 0406 return window()->createTextureFromImage(image, QQuickWindow::TextureIsOpaque); 0407 }; 0408 } 0409 0410 void PipeWireSourceItem::componentComplete() 0411 { 0412 QQuickItem::componentComplete(); 0413 if (d->m_nodeId != 0) { 0414 refresh(); 0415 } 0416 } 0417 0418 PipeWireSourceItem::StreamState PipeWireSourceItem::state() const 0419 { 0420 if (!d->m_stream) { 0421 return StreamState::Unconnected; 0422 } 0423 switch (d->m_stream->state()) { 0424 case PW_STREAM_STATE_ERROR: 0425 return StreamState::Error; 0426 case PW_STREAM_STATE_UNCONNECTED: 0427 return StreamState::Unconnected; 0428 case PW_STREAM_STATE_CONNECTING: 0429 return StreamState::Connecting; 0430 case PW_STREAM_STATE_PAUSED: 0431 return StreamState::Paused; 0432 case PW_STREAM_STATE_STREAMING: 0433 return StreamState::Streaming; 0434 default: 0435 return StreamState::Error; 0436 } 0437 } 0438 0439 uint PipeWireSourceItem::fd() const 0440 { 0441 return d->m_fd.value_or(0); 0442 } 0443 0444 uint PipeWireSourceItem::nodeId() const 0445 { 0446 return d->m_nodeId; 0447 } 0448 0449 QSize PipeWireSourceItem::streamSize() const 0450 { 0451 if (!d->m_stream) { 0452 return QSize(); 0453 } 0454 return d->m_stream->size(); 0455 } 0456 0457 bool PipeWireSourceItem::usingDmaBuf() const 0458 { 0459 return d->m_stream && d->m_stream->usingDmaBuf(); 0460 } 0461 0462 bool PipeWireSourceItem::allowDmaBuf() const 0463 { 0464 return d->m_stream && d->m_stream->allowDmaBuf(); 0465 } 0466 0467 void PipeWireSourceItem::setAllowDmaBuf(bool allowed) 0468 { 0469 d->m_allowDmaBuf = allowed; 0470 if (d->m_stream) { 0471 d->m_stream->setAllowDmaBuf(allowed); 0472 } 0473 } 0474 0475 #include "moc_pipewiresourceitem.cpp"