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