【问题标题】:Qt - QML Screen Mirroring in a dual monitor setupQt - 双显示器设置中的 QML 屏幕镜像
【发布时间】:2021-12-11 04:34:43
【问题描述】:

我开发了一个应用程序,其 UI 使用 QML 完成。我现在被要求做的是,当使用第二个监视器时,第二个监视器显示程序正在执行的所有操作。起初我只想告诉客户端配置 Windows 以克隆它的屏幕。但是,当应用程序使用它的一些功能时,我需要克隆屏幕在克隆屏幕上显示某些指标,而不是在原始屏幕上显示。

所以我的问题是,我怎样才能做到这一点。如何在一个屏幕上反映正在发生的事情,同时保持足够的控制来在一个屏幕上绘制而不是在另一个屏幕上绘制。

我唯一的想法是使用计时器定期拍摄屏幕截图并在第二个屏幕上显示该图像。

这可行吗?

【问题讨论】:

    标签: qt qml


    【解决方案1】:

    你可以use particular QML elements as texture sources and easily duplicate them via trivial fragment shaders

    您绝对不想截屏并将图像绘制回来,这非常低效。

    请记住,它只是一个视觉副本,不会接受用户输入。如果您希望它在两个窗口中都具有交互性,那么您应该只使用一个数据对象并将其连接到两个单独的 GUI 元素。

    好的,这是代码,但不幸的是,它显然发现了 QML 中的一个错误,因为该实现似乎无法跨不同的窗口工作:

    Window {
      id: mainw
      visible: true
      width: 640
      height: 480
      title: qsTr("main window")
    
      Row {
        spacing: 5
        Rectangle {
          id: source
          width: 100
          height: 100
          color: ma.containsPress ? "red" : "cyan"
          Text {
            text: "adoy"
            anchors.centerIn: parent
          }
          MouseArea {
            id: ma
            anchors.fill: parent
          }
        }
        ShaderEffectSource {
          width: source.width
          height: source.height
          live: true
          sourceItem: source
        }
      }
    
      Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("another window")
        x: mainw.x + width + 10
        y: mainw.y
        Row {
          ShaderEffectSource {
            width: source.width
            height: source.height
            live: true
            sourceItem: source
          }
          Rectangle {
            width: 100
            height: 100
            color: "blue"
          }
        }
      }
    }
    

    【讨论】:

    • 这正是我想要的(我的意思是没有用户输入)。但是我不知道如何(或者坦率地说你在说什么)在你的建议中。从你给我的链接我看不出怎么做。有什么地方可以拿到 MWE 吗?比如说用一个简单的窗口和一个按钮之类的东西做我想做的事?
    • 好的,我会记住这一点,但它不适用于不同的窗口...
    • @aarelovich 我提交了一个错误报告,看看情况如何。同时你可以追求其他方向。
    • @JavierCordero 不幸的是,他们认为这太难修复了:bugreports.qt.io/browse/QTBUG-68910?filter=-2
    • @JavierCordero 如果显示器在同一个 gpu 上,他们至少应该支持它,否则失败。在绝大多数情况下,这已经足够好了。我不希望通过 pcie 或其他方式在不同的适配器之间共享缓冲区......
    【解决方案2】:

    定期截屏虽然完全可行,但由于会影响性能,因此不受欢迎。相反,您应该使用主窗口的onFrameSwapped() 信号,仅在生成新帧时抓取图像。

    理想情况下,您会想要使用 Layer 或 ShaderEffectSource,正如 @dtech 所建议的那样,直接从 GPU 读取和重新渲染帧。不幸的是,由于 Qt Quick 的场景图的限制,不可能在不破坏源窗口的场景图的情况下跨单独的窗口完成此操作。

    选项是通过 CPU 复制帧,或者在 C++ 中重新实现部分 QML 引擎以在生成新帧时提取图像。

    有效的 QML 解决方案

    最简单的方法是从 QML 中抓取帧,使用 Item 的 grabToImage() 函数。由于 Window 本身不是一个项目,因此您必须从其中一个元素中获取图像。在此示例中,每次在 mainWindow 上交换帧时,我使用 onFrameSwapped() 信号从名为 viewport 的项目中获取图像。然后将内存中该图像的路径设置为第二个窗口中图像的源,名为 projectionWindow。第二个窗口将在 screenID 变量设置的屏幕上打开;它也被设置为无框窗口,其可见性设置为最大化或全屏,因此它是在第二个屏幕上看到的唯一窗口。

    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {
        id: mainWindow
    
        title: qsTr("Main Window")
        visible: true
        width: 400
        height: 200
        color: "#0F0"
    
        Rectangle {
            id: viewport
            color: "#00F"
            width: parent.width/2
            height: parent.height/2
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter
        }
    
        onFrameSwapped: {
            viewport.grabToImage(function(result) {
                projectionWindow.frame = String(result.url);
            });
        }
    
        Window {
            id: projectionWindow
    
            property int screenID: 1
            property alias frame: img.source
    
            transientParent: mainWindow
            visible: true
            x: Qt.application.screens[screenID].virtualX
            y: Qt.application.screens[screenID].virtualY
            width: Qt.application.screens[screenID].desktopAvailableWidth
            height: Qt.application.screens[screenID].desktopAvailableHeight
            flags: Qt.FramelessWindowHint
            color: "#000"
            visibility: Window.Maximized
            // visibility: Window.FullScreen
    
            Image {
                id: img
                anchors.fill: parent
                fillMode: Image.PreserveAspectFit
                // Performance tweaks
                asynchronous: true
                cache: false
            }
        }
    }
    

    我实际上已经在我开发的一个项目中使用了这个解决方案。您可以在以下位置学习完整的实现:

    替代 C++ 解决方案

    第二种更有效但不完整的解决方案超出了我目前的经验水平。本质上,您将从 QQuickWindow 继承并将图像发送到QQuickWindow::afterRendering()QQuickWindow::frameSwapped() 信号上的第二个窗口。您将使用QQuickFramebufferObject 来渲染和抓取屏幕外的帧,这意味着使用 OpenGL 作为您的渲染器,这可能会限制 Qt 6 中的平台可移植性,或者至少会抵消使用本机渲染管道带来的所有性能优势。

    https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html

    Giuseppe D'Angelo 的以下演讲展示了其中一些是如何设置的。它没有显示如何将内容复制到另一个窗口/屏幕,但它可以在这方面有所帮助。

    https://www.youtube.com/watch?v=V_idc9BBRuI

    https://www.youtube.com/watch?v=D-7fVGIBz6k

    【讨论】:

      猜你喜欢
      • 2014-01-27
      • 1970-01-01
      • 2014-09-28
      • 2020-02-21
      • 2017-09-13
      • 2020-12-23
      • 2023-03-08
      • 2011-09-20
      • 1970-01-01
      相关资源
      最近更新 更多