3
0
mirror of https://github.com/Qortal/Brooklyn.git synced 2025-02-22 15:15:53 +00:00
2022-03-05 22:41:29 +05:00

299 lines
9.4 KiB
C++

/*
Render a PipeWire stream into a QtQuick scene as a standard Item
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "pipewiresourceitem.h"
#include "logging.h"
#include "pipewiresourcestream.h"
#include <QGuiApplication>
#include <QOpenGLContext>
#include <QOpenGLTexture>
#include <QQuickWindow>
#include <QRunnable>
#include <QSGImageNode>
#include <QSocketNotifier>
#include <QThread>
#include <qpa/qplatformnativeinterface.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <libdrm/drm_fourcc.h>
#include <QtPlatformHeaders/QEGLNativeContext>
static void pwInit()
{
pw_init(nullptr, nullptr);
}
Q_COREAPP_STARTUP_FUNCTION(pwInit);
class DiscardEglPixmapRunnable : public QRunnable
{
public:
DiscardEglPixmapRunnable(EGLImageKHR image, QOpenGLTexture *texture)
: m_image(image)
, m_texture(texture)
{
}
void run() override
{
if (m_image != EGL_NO_IMAGE_KHR) {
static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
eglDestroyImageKHR(eglGetCurrentDisplay(), m_image);
}
delete m_texture;
}
private:
const EGLImageKHR m_image;
QOpenGLTexture *m_texture;
};
PipeWireSourceItem::PipeWireSourceItem(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents, true);
connect(this, &QQuickItem::visibleChanged, this, [this]() {
setEnabled(isVisible());
if (m_stream)
m_stream->setActive(isVisible());
});
}
PipeWireSourceItem::~PipeWireSourceItem()
{
}
void PipeWireSourceItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
{
switch (change) {
case ItemVisibleHasChanged:
setEnabled(isVisible());
if (m_stream)
m_stream->setActive(isVisible() && data.boolValue && isComponentComplete());
break;
case ItemSceneChange:
m_needsRecreateTexture = true;
releaseResources();
break;
default:
break;
}
}
void PipeWireSourceItem::releaseResources()
{
if (window()) {
window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_image, m_texture.take()), QQuickWindow::NoStage);
m_image = EGL_NO_IMAGE_KHR;
}
}
void PipeWireSourceItem::setNodeId(uint nodeId)
{
if (nodeId == m_nodeId)
return;
m_nodeId = nodeId;
setEnabled(false);
if (m_nodeId == 0) {
m_stream.reset(nullptr);
m_createNextTexture = [] {
return nullptr;
};
} else {
m_stream.reset(new PipeWireSourceStream(this));
m_stream->createStream(m_nodeId);
if (!m_stream->error().isEmpty()) {
m_stream.reset(nullptr);
m_nodeId = 0;
return;
}
m_stream->setActive(isVisible() && isComponentComplete());
connect(m_stream.data(), &PipeWireSourceStream::dmabufTextureReceived, this, &PipeWireSourceItem::updateTextureDmaBuf);
connect(m_stream.data(), &PipeWireSourceStream::imageTextureReceived, this, &PipeWireSourceItem::updateTextureImage);
}
Q_EMIT nodeIdChanged(nodeId);
}
QSGNode *PipeWireSourceItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
{
if (Q_UNLIKELY(!m_createNextTexture)) {
return node;
}
auto texture = m_createNextTexture();
if (!texture) {
delete node;
return nullptr;
}
if (m_needsRecreateTexture) {
delete node;
node = nullptr;
m_needsRecreateTexture = false;
}
QSGImageNode *textureNode = static_cast<QSGImageNode *>(node);
if (!textureNode) {
textureNode = window()->createImageNode();
textureNode->setOwnsTexture(true);
}
textureNode->setTexture(texture);
const auto br = boundingRect().toRect();
QRect rect({0, 0}, texture->textureSize().scaled(br.size(), Qt::KeepAspectRatio));
rect.moveCenter(br.center());
textureNode->setRect(rect);
return textureNode;
}
QString PipeWireSourceItem::error() const
{
return m_stream->error();
}
static EGLImage createImage(EGLDisplay display, const QVector<DmaBufPlane> &planes, uint32_t format, const QSize &size)
{
const bool hasModifiers = planes[0].modifier != DRM_FORMAT_MOD_INVALID;
QVector<EGLint> attribs;
attribs << EGL_WIDTH << size.width() << EGL_HEIGHT << size.height() << EGL_LINUX_DRM_FOURCC_EXT << EGLint(format)
<< EGL_DMA_BUF_PLANE0_FD_EXT << planes[0].fd << EGL_DMA_BUF_PLANE0_OFFSET_EXT << EGLint(planes[0].offset) << EGL_DMA_BUF_PLANE0_PITCH_EXT
<< EGLint(planes[0].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(planes[0].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
<< EGLint(planes[0].modifier >> 32);
}
if (planes.count() > 1) {
attribs << EGL_DMA_BUF_PLANE1_FD_EXT << planes[1].fd << EGL_DMA_BUF_PLANE1_OFFSET_EXT << EGLint(planes[1].offset) << EGL_DMA_BUF_PLANE1_PITCH_EXT
<< EGLint(planes[1].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(planes[1].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
<< EGLint(planes[1].modifier >> 32);
}
}
if (planes.count() > 2) {
attribs << EGL_DMA_BUF_PLANE2_FD_EXT << planes[2].fd << EGL_DMA_BUF_PLANE2_OFFSET_EXT << EGLint(planes[2].offset) << EGL_DMA_BUF_PLANE2_PITCH_EXT
<< EGLint(planes[2].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(planes[2].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
<< EGLint(planes[2].modifier >> 32);
}
}
if (planes.count() > 3) {
attribs << EGL_DMA_BUF_PLANE3_FD_EXT << planes[3].fd << EGL_DMA_BUF_PLANE3_OFFSET_EXT << EGLint(planes[3].offset) << EGL_DMA_BUF_PLANE3_PITCH_EXT
<< EGLint(planes[3].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(planes[3].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT
<< EGLint(planes[3].modifier >> 32);
}
}
attribs << EGL_NONE;
static auto eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
Q_ASSERT(eglCreateImageKHR);
EGLImage ret = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data());
if (ret == EGL_NO_IMAGE_KHR) {
qCWarning(PIPEWIRE_LOGGING) << "invalid image" << glGetError();
}
// Q_ASSERT(ret);
return ret;
}
void PipeWireSourceItem::updateTextureDmaBuf(const QVector<DmaBufPlane> &planes, uint32_t format)
{
static auto s_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!s_glEGLImageTargetTexture2DOES) {
qCWarning(PIPEWIRE_LOGGING) << "glEGLImageTargetTexture2DOES is not available" << window();
return;
}
if (!window() || !window()->openglContext() || !m_stream) {
qCWarning(PIPEWIRE_LOGGING) << "need a window and a context" << window();
return;
}
const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
if (m_image) {
static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
eglDestroyImageKHR(display, m_image);
}
const auto size = m_stream->size();
m_image = createImage(display, planes, format, size);
if (m_image == EGL_NO_IMAGE_KHR) {
QImage img(200, 200, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::blue);
updateTextureImage(img);
return;
}
m_createNextTexture = [this, size, format] {
if (!m_texture) {
m_texture.reset(new QOpenGLTexture(QOpenGLTexture::Target2D));
bool created = m_texture->create();
Q_ASSERT(created);
}
m_texture->bind();
s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_image);
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_texture->release();
m_texture->setSize(size.width(), size.height());
int textureId = m_texture->textureId();
QQuickWindow::CreateTextureOption textureOption = format == DRM_FORMAT_ARGB8888 ? QQuickWindow::TextureHasAlphaChannel : QQuickWindow::TextureIsOpaque;
setEnabled(true);
return window()->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &textureId, 0 /*a vulkan thing?*/, size, textureOption);
};
if (window()->isVisible()) {
update();
}
}
void PipeWireSourceItem::updateTextureImage(const QImage &image)
{
if (!window()) {
qCWarning(PIPEWIRE_LOGGING) << "pass";
return;
}
m_createNextTexture = [this, image] {
setEnabled(true);
return window()->createTextureFromImage(image, QQuickWindow::TextureIsOpaque);
};
if (window()->isVisible())
update();
}
void PipeWireSourceItem::componentComplete()
{
if (m_stream)
m_stream->setActive(isVisible());
QQuickItem::componentComplete();
}