Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qeglfsscreencapture_p.h"
++
++ #include <QtMultimedia/private/qsurfacecapturegrabber_p.h>
++ #include "qguiapplication.h"
++ #include "qopenglvideobuffer_p.h"
++ #include "private/qimagevideobuffer_p.h"
++ #include "private/qvideoframe_p.h"
++
++ #include <QtMultimedia/private/qmultimedia_ranges_p.h>
++ #include <QtOpenGL/private/qopenglcompositor_p.h>
++ #include <QtOpenGL/private/qopenglframebufferobject_p.h>
++
++ #ifndef QT_NO_QUICK
++ #include <QtQuick/qquickwindow.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++ namespace ranges = QtMultimediaPrivate::ranges;
++
++ class QEglfsScreenCapture::Grabber : public QSurfaceCaptureGrabber
++ {
++ public:
++ Grabber(QEglfsScreenCapture &screenCapture, QScreen *screen)
++ : QSurfaceCaptureGrabber(QSurfaceCaptureGrabber::UseCurrentThread)
++ {
++ addFrameCallback(&screenCapture, &QEglfsScreenCapture::newVideoFrame);
++ connect(this, &Grabber::errorUpdated, &screenCapture, &QEglfsScreenCapture::updateError);
++
++ Q_ASSERT(screen);
++ setFrameRate(screenCapture.frameRate().value_or(qMin(screen->refreshRate(), 30.f)));
++ }
++
++ ~Grabber() override { stop(); }
++
++ QVideoFrameFormat format() { return m_format; }
++
++ protected:
++ QVideoFrame grabFrame() override
++ {
++ auto nativeSize = QOpenGLCompositor::instance()->nativeTargetGeometry().size();
++ auto fbo = std::make_unique<QOpenGLFramebufferObject>(nativeSize);
++
++ if (!QOpenGLCompositor::instance()->grabToFrameBufferObject(
++ fbo.get(), QOpenGLCompositor::NotFlipped)) {
++ updateError(Error::InternalError, QLatin1String("Couldn't grab to framebuffer object"));
++ return {};
++ }
++
++ if (!fbo->isValid()) {
++ updateError(Error::InternalError, QLatin1String("Framebuffer object invalid"));
++ return {};
++ }
++
++ auto videoBuffer = std::make_unique<QOpenGLVideoBuffer>(std::move(fbo));
++
++ if (!m_format.isValid()) {
++ auto image = videoBuffer->ensureImageBuffer().underlyingImage();
++ m_format = { image.size(),
++ QVideoFrameFormat::pixelFormatFromImageFormat(image.format()) };
++ }
++
++ if (m_format.streamFrameRate() != activeFrameRate())
++ m_format.setStreamFrameRate(activeFrameRate());
++
++ return QVideoFramePrivate::createFrame(std::move(videoBuffer), m_format);
++ }
++
++ QVideoFrameFormat m_format;
++ };
++
++ #ifndef QT_NO_QUICK
++ class QEglfsScreenCapture::QuickGrabber : public Grabber
++ {
++ public:
++ QuickGrabber(QEglfsScreenCapture &screenCapture, QScreen *screen, QQuickWindow *quickWindow)
++ : Grabber(screenCapture, screen), m_quickWindow(quickWindow)
++ {
++ Q_ASSERT(m_quickWindow);
++ }
++
++ protected:
++ QVideoFrame grabFrame() override
++ {
++ if (!m_quickWindow) {
++ updateError(Error::InternalError, QLatin1String("Window deleted"));
++ return {};
++ }
++
++ QImage image = m_quickWindow->grabWindow();
++
++ if (image.isNull()) {
++ updateError(Error::InternalError, QLatin1String("Image invalid"));
++ return {};
++ }
++
++ if (!m_format.isValid()) {
++ m_format = { image.size(),
++ QVideoFrameFormat::pixelFormatFromImageFormat(image.format()) };
++ }
++
++ if (m_format.streamFrameRate() != activeFrameRate())
++ m_format.setStreamFrameRate(activeFrameRate());
++
++ return QVideoFramePrivate::createFrame(
++ std::make_unique<QImageVideoBuffer>(std::move(image)), m_format);
++ }
++
++ private:
++ QPointer<QQuickWindow> m_quickWindow;
++ };
++ #endif // QT_NO_QUICK
++
++ QEglfsScreenCapture::QEglfsScreenCapture() : QPlatformSurfaceCapture(ScreenSource{}) { }
++
++ QEglfsScreenCapture::~QEglfsScreenCapture() = default;
++
++ QVideoFrameFormat QEglfsScreenCapture::frameFormat() const
++ {
++ return m_grabber ? m_grabber->format() : QVideoFrameFormat();
++ }
++
++ bool QEglfsScreenCapture::setActiveInternal(bool active)
++ {
++ if (static_cast<bool>(m_grabber) == active)
++ return true;
++
++ if (m_grabber)
++ m_grabber.reset();
++
++ if (!active)
++ return true;
++
++ m_grabber = createGrabber();
++
++ if (!m_grabber) {
++ // TODO: This could mean that the UI is not started yet, so we should wait and try again,
++ // and then give error if still not started. Might not be possible here.
++ return false;
++ }
++
++ m_grabber->start();
++ return true;
++ }
++
++ bool QEglfsScreenCapture::isSupported()
++ {
++ return QGuiApplication::platformName() == QLatin1String("eglfs");
++ }
++
++ std::unique_ptr<QEglfsScreenCapture::Grabber> QEglfsScreenCapture::createGrabber()
++ {
++ auto screen = source<ScreenSource>();
++ if (!checkScreenWithError(screen))
++ return nullptr;
++
++ QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
++
++ if (compositor->context() && QOpenGLContext::currentContext()) {
++ // Create OpenGL grabber
++ if (!compositor->targetWindow()) {
++ updateError(Error::CaptureFailed,
++ QLatin1String("Target window is not set for OpenGL compositor"));
++ return nullptr;
++ }
++
++ return std::make_unique<Grabber>(*this, screen);
++ }
++
++ #ifndef QT_NO_QUICK
++ // Check for QQuickWindow
++ auto windows = QGuiApplication::topLevelWindows();
++ auto it = ranges::find_if(windows, [screen](QWindow *window) {
++ auto quickWindow = qobject_cast<QQuickWindow *>(window);
++ if (!quickWindow)
++ return false;
++
++ return quickWindow->screen() == screen;
++ });
++
++ if (it != windows.end()) {
++ // Create grabber that calls QQuickWindow::grabWindow
++ return std::make_unique<QuickGrabber>(*this, screen, qobject_cast<QQuickWindow *>(*it));
++ }
++ #endif // QT_NO_QUICK
++
++ updateError(Error::CaptureFailed, QLatin1String("No existing OpenGL context or QQuickWindow"));
++ return nullptr;
++ }
++
++ QT_END_NAMESPACE