EGLFS下的双屏异显

目录

QMainWindow 

QOpenGLWidget

两个 QOpenGLWidget 分布在两个顶层窗口中

单窗口跨双屏

每个屏幕自有drm buffer

双屏共享一个DRM buffer

双QOpenGLWindow

控制窗口

视频窗口

一些参考

总结


QMainWindow 

代码报错如下: EGLFS: OpenGL windows cannot be mixed with others.

网上资料显示这个问题常见,例如: 解决 eglfs: opengl windows cannot be mixed with others.aborted 错误的实战指南_指针PPPPoi-音视频技术专区

原因:EGLFS 仅支持单个原生窗口

EGLFS(EGL Full Screen)是无窗口管理器的平台插件,整个应用程序只能有一个原生窗口(native window)和一个 EGL surface

代码创建了两个 ScreenWindow(两个 QMainWindow),每个都会尝试申请一个独立的原生窗口,这在 EGLFS 下直接触发报错。

class ScreenWindow : public QMainWindow {
public:
    ScreenWindow(QScreen* screen, int screenIndex, QWidget* contentWidget)
        : QMainWindow(nullptr)
        , m_screenIndex(screenIndex) {
        
        // EGLFS下必须使用无边框窗口
        setWindowFlags(Qt::FramelessWindowHint);
        
        // 设置中央控件
        setCentralWidget(contentWidget);
        
        // 【关键】先创建窗口句柄,然后设置屏幕
        createWinId();
        QWindow* window = windowHandle();
        if (window && screen) {
            window->setScreen(screen);
            LOG_INFO("ScreenWindow %d 绑定到屏幕: %s", 
                     screenIndex, screen->name().toUtf8().constData());
        } else {
            LOG_WARN("ScreenWindow %d 未能绑定到屏幕", screenIndex);
        }
        
        // 设置背景
        setStyleSheet("background-color: #1a1a2e;");
    }
    
    void showScreen() {
        // EGLFS下使用showFullScreen()而非show()
        showFullScreen();
        LOG_INFO("ScreenWindow %d 已全屏显示", m_screenIndex);
    }
    
private:
    int m_screenIndex;
};
int main(int argc, char* argv[]) {
    // 环境配置
    qputenv("QT_QPA_PLATFORM", "eglfs");
    qputenv("QT_QPA_EGLFS_INTEGRATION", "eglfs_kms");
    qputenv("QT_QPA_EGLFS_KMS_ATOMIC", "0");
    qputenv("QT_QPA_EGLFS_KMS_CONFIG", "/etc/kms_dual.json");
    setenv("DISPLAY", "", 1);
    
    // 启用OpenGL上下文共享
    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

    // 日志系统
    Logger::instance()->setLevel(LOG_DEBUG);
    Logger::instance()->setConsoleOutput(true);
    Logger::instance()->setFileOutput("./logs/video_sdk.log", true);
    LOG_INFO("=== 双屏异显异触 Demo 启动 (EGLFS/KMS适配版) ===");

    

    // 创建QApplication
    QApplication app(argc, argv);

    // 获取屏幕列表
    auto screens = app.screens();
    if (screens.size() < 2) {
        LOG_ERROR("需要至少2个屏幕,当前只有 %zu 个", screens.size());
        return -1;
    }
    
    

    // 创建视频控件
    VideoWidget* mainVideo = new VideoWidget();
    mainVideo->setDisplayMode(1);
    mainVideo->setSource(0, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    mainVideo->setSource(1, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    
    VideoWidget* subVideo = new VideoWidget();
    subVideo->setDisplayMode(1);
    subVideo->setSource(0, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    subVideo->setSource(1, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);

    // 创建屏幕内容
    MainScreenWidget* mainContent = new MainScreenWidget(mainVideo);
    SubScreenWidget* subContent = new SubScreenWidget(subVideo);

    // 创建窗口并绑定屏幕
    ScreenWindow mainWindow(screens[0], 0, mainContent);
    mainWindow.showScreen();
    LOG_INFO("主屏窗口已创建并全屏显示");

    ScreenWindow subWindow(screens[1], 1, subContent);
    subWindow.showScreen();
    LOG_INFO("副屏窗口已创建并全屏显示");

QOpenGLWidget

QOpenGLWidget 内部依赖 OpenGL 渲染,而 QMainWindow/QWidget 默认是 软件渲染(Raster)。EGLFS 严格禁止将 OpenGL 窗口与 Raster 窗口混合

两个 QOpenGLWidget 分布在两个顶层窗口中


即使只创建一个 QMainWindow,如果 VideoWidget(QOpenGLWidget)需要独立的 OpenGL 上下文和帧缓冲,EGLFS 也会将其视为额外的 OpenGL 窗口。

目前一个屏幕显示流,一个屏幕显示控件正常。

但两路都是显示视频流,即两路OPENGL,则存在问题。

两路GL ,在不同线程(各自的paintGL)里面,同时渲染。

单窗口跨双屏

每个屏幕自有drm buffer

    

BOM Management and PCB | Mermaid Chart

双屏共享一个DRM buffer

方案是: 采用一个DRM BUFFER(两个屏幕之和大小的DRM buffer),视频源,渲染到此buffer的不同位置对应不同屏幕,然后实现异显。实际效果只有一个屏幕显示

eglfs_kms 后端为每个屏幕创建独立的 EGL surface, DualScreenWindow 只绑定到了主屏的 1920×1080 surface

/**
 * @file main_dual.cpp
 * @brief 双屏异显演示程序 - 共享纹理方案
 */

#include <QApplication>
#include <QMainWindow>
#include <QScreen>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QTimer>
#include <QWindow>
#include <QDebug>
#include <QThread>

#include "../src/display/VideoWidget_p.h"
#include "../src/utils/Logger.h"
#include "../src/utils/ThreadManager.h"
#include "../src/input/TouchManager.h"

// ============================================================
//  配置宏
// ============================================================

#define SCREEN_WIDTH   1920
#define SCREEN_HEIGHT  1080

#define TOUCH_DEVICE_1 "/dev/input/event4"
#define TOUCH_DEVICE_2 "/dev/input/event3"

#define VIDEO_SOURCE "/home/kylin/1600120060fps.ts"
#define VIDEO_WIDTH  1920
#define VIDEO_HEIGHT 1080

// ============================================================
//  主窗口
// ============================================================

class DualScreenWindow : public QMainWindow {
   // Q_OBJECT

public:
    DualScreenWindow(VideoWidget* video, QWidget* parent = nullptr)
        : QMainWindow(parent)
        , m_video(video) {

        setWindowFlags(Qt::FramelessWindowHint);
        setStyleSheet("background-color: #1a1a2e;");

        QWidget* central = new QWidget(this);
        setCentralWidget(central);

        QVBoxLayout* layout = new QVBoxLayout(central);
        layout->setContentsMargins(0, 0, 0, 0);
        layout->setSpacing(0);

        if (m_video) {
            m_video->setParent(central);
            layout->addWidget(m_video, 10);
        }

        QWidget* ctrlBar = createControlBar();
        ctrlBar->setFixedHeight(100);
        ctrlBar->setStyleSheet("background-color: rgba(42, 42, 62, 200);");
        layout->addWidget(ctrlBar);

        LOG_INFO("DualScreenWindow created");
    }

private:
    QWidget* createControlBar() {
        QWidget* ctrlBar = new QWidget(this);

        QHBoxLayout* ctrlLayout = new QHBoxLayout(ctrlBar);
        ctrlLayout->setContentsMargins(50, 15, 50, 15);
        ctrlLayout->setSpacing(20);

        // 通道切换按钮
        QPushButton* btnSwitch = new QPushButton("切换通道", this);
        btnSwitch->setFixedSize(120, 45);
        btnSwitch->setStyleSheet(
            "QPushButton {"
            "   background-color: #3a5a8e;"
            "   color: white;"
            "   font-size: 16px;"
            "   border-radius: 6px;"
            "}"
            "QPushButton:pressed {"
            "   background-color: #5a7aae;"
            "}"
        );
        ctrlLayout->addWidget(btnSwitch);

        ctrlLayout->addStretch();

        m_statusLabel = new QLabel("双屏运行中 | 共享纹理", this);
        m_statusLabel->setStyleSheet(
            "color: #88cc88;"
            "font-size: 18px;"
            "font-weight: bold;"
            "background-color: transparent;"
        );
        ctrlLayout->addWidget(m_statusLabel);

        ctrlLayout->addStretch();

        QPushButton* shutdownBtn = new QPushButton("关 机", this);
        shutdownBtn->setFixedSize(100, 45);
        shutdownBtn->setStyleSheet(
            "QPushButton {"
            "   background-color: #cc4444;"
            "   color: white;"
            "   font-size: 16px;"
            "   border-radius: 6px;"
            "}"
            "QPushButton:pressed {"
            "   background-color: #aa2222;"
            "}"
        );
        ctrlLayout->addWidget(shutdownBtn);

        connect(btnSwitch, &QPushButton::clicked, [this]() {
            LOG_INFO("切换通道");
            if (m_video) {
                static int ch = 0;
                ch = (ch + 1) % 4;
                m_video->setSource(ch, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
                m_statusLabel->setText(QString("双屏运行中 | 通道%1").arg(ch));
            }
        });

        connect(shutdownBtn, &QPushButton::clicked, []() {
            LOG_INFO("关机按钮被点击");
            // system("sudo poweroff");
        });

        return ctrlBar;
    }

private:
    VideoWidget* m_video;
    QLabel* m_statusLabel;
};
// main_dual.cpp 添加

// ============================================================
//  main
// ============================================================

int main(int argc, char* argv[]) {
    // 环境配置
    qputenv("QT_QPA_PLATFORM", "eglfs");
    qputenv("QT_QPA_EGLFS_INTEGRATION", "eglfs_kms");
    qputenv("QT_QPA_EGLFS_KMS_ATOMIC", "0");
    qputenv("QT_QPA_EGLFS_KMS_CONFIG", "/etc/kms_dual.json");
    //setenv("DISPLAY", "", 1);

    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

    Logger::instance()->setLevel(LOG_DEBUG);
    Logger::instance()->setConsoleOutput(true);
    Logger::instance()->setFileOutput("./logs/video_sdk.log", true);
    LOG_INFO("=== 双屏异显 Demo 启动 (共享纹理方案) ===");

    ThreadManager::instance()->setDefaultConfig();
    ThreadManager::instance()->applyCurrentThread("main");

    QApplication app(argc, argv);

    auto screens = app.screens();
    if (screens.size() < 2) {
        LOG_ERROR("需要至少2个屏幕,当前只有 %zu 个", screens.size());
        return -1;
    }

    int screenWidth = screens[0]->geometry().width();
    int screenHeight = screens[0]->geometry().height();
    int totalWidth = screenWidth * 2;

    LOG_INFO("屏幕0: %s %dx%d", screens[0]->name().toUtf8().constData(),
             screenWidth, screenHeight);
    LOG_INFO("屏幕1: %s %dx%d", screens[1]->name().toUtf8().constData(),
             screenWidth, screenHeight);
    LOG_INFO("虚拟桌面尺寸: %dx%d", totalWidth, screenHeight);

 

    // 创建VideoWidget
    VideoWidget* video = new VideoWidget();

    // 启用双屏模式
    video->setDualScreenMode(true, screenWidth, screenHeight);

    // 设置视频源(只用通道0)
    video->setSource(0, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    video->setSource(1, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    video->setSource(2, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
    video->setSource(3, VIDEO_SOURCE, 0, VIDEO_WIDTH, VIDEO_HEIGHT);

    // 创建窗口
    DualScreenWindow window(video);
    window.setMinimumSize(totalWidth, screenHeight);
    window.showFullScreen();
    LOG_INFO("双屏窗口已创建并全屏显示");

 
    // 启动视频
    video->start();
    LOG_INFO("视频流已启动");

    

    LOG_INFO("触摸设备已注册");
    LOG_INFO("=== 系统运行中,按 Ctrl+C 退出 ===");

    int ret = app.exec();

    video->stop();
    LOG_INFO("=== 程序正常退出 ===");

    return ret;
}

//#include "main_dual.moc"

DualScreenWindow (QMainWindow)
├── centralWidget (QWidget)
│   └── QVBoxLayout (margins=0, spacing=0)
│       ├── VideoWidget (QOpenGLWidget, stretch=10)
│       └── controlBar (QWidget, height=100, bg=rgba(42,42,62,200))
│           └── QHBoxLayout (margins=50,15,50,15, spacing=20)
│               ├── btnSwitch (QPushButton, "切换通道", 120×45, #3a5a8e)
│               ├── Stretch
│               ├── statusLabel (QLabel, "双屏运行中 | 共享纹理", #88cc88, 18px bold)
│               ├── Stretch
│               └── btnShutdown (QPushButton, "关 机", 100×45, #cc4444)

双QOpenGLWindow

即每个屏幕用一个 QOpenGLWindow去完成显示,则是OK的。每个有自己的opengl接口。

但我们需要将功能封装成widget。不能继承自这个类。

继承 QOpenGLWindow继承 QOpenGLWidget
是一个 QWindow,不是 QWidget是一个 QWidget
不能直接添加 QPushButton/QLabel 等控件可以添加子控件
适合纯 OpenGL 渲染,无 UI 叠加适合视频 + UI 叠加
需要单独 show()可以嵌入到 QWidget 布局中

控制窗口

// ============================================================
// 8. 控制窗口 (HDMI2)
// ============================================================
class ControlWindow : public QOpenGLWindow
{
public:
    ControlWindow(QScreen* screen) {
        setScreen(screen);
        setGeometry(0, 0, 1920, 1080);
        
        btnColors[0] = QColor(100, 100, 200);
        btnColors[1] = QColor(100, 200, 100);
        btnColors[2] = QColor(200, 100, 100);
        bgColor = QColor(30, 30, 50);
    }
    
protected:
    void paintGL() override {
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        
        painter.fillRect(0, 0, 1920, 1080, bgColor);
        
        painter.setPen(Qt::white);
        painter.setFont(QFont("Arial", 36, QFont::Bold));
        painter.drawText(50, 80, "HDMI2 - 控制面板");
        
        rectA = QRect(100, 200, 200, 80);
        rectB = QRect(400, 200, 200, 80);
        rectC = QRect(700, 200, 200, 80);
        
        drawButton(painter, rectA, "按钮A", btnColors[0]);
        drawButton(painter, rectB, "按钮B", btnColors[1]);
        drawButton(painter, rectC, "按钮C", btnColors[2]);
        
        painter.setPen(Qt::yellow);
        painter.setFont(QFont("Arial", 24));
        painter.drawText(50, 400,
            QString("触摸: (%1, %2)  %3")
            .arg(touchX).arg(touchY)
            .arg(touching ? "按下" : "释放"));
        
        colorRect1 = QRect(100, 500, 120, 80);
        colorRect2 = QRect(300, 500, 120, 80);
        colorRect3 = QRect(500, 500, 120, 80);
        painter.fillRect(colorRect1, Qt::red);
        painter.fillRect(colorRect2, Qt::green);
        painter.fillRect(colorRect3, Qt::blue);
        painter.setPen(Qt::white);
        painter.drawRect(colorRect1);
        painter.drawRect(colorRect2);
        painter.drawRect(colorRect3);
        
        painter.setFont(QFont("Arial", 16));
        painter.drawText(colorRect1, Qt::AlignCenter, "红色");
        painter.drawText(colorRect2, Qt::AlignCenter, "绿色");
        painter.drawText(colorRect3, Qt::AlignCenter, "蓝色");
        

视频窗口

// ============================================================
// 7. 视频窗口 (HDMI1)
// ============================================================
class VideoWindow : public QOpenGLWindow
{
public:
    VideoWindow(QScreen* screen, int idx)
        : screenIdx(idx) {
        setScreen(screen);
        setGeometry(0, 0, 1920, 1080);
        
        for (int i = 0; i < 2; i++) {
            textures[i] = nullptr;
            active_streams[i] = false;
        }
        
        display_mode = DISPLAY_MODE_1CH;
        active_streams[0] = true;
        active_count = 1;
        
        sim_cap = new SimCapture();
    }
    
    ~VideoWindow() {
        delete sim_cap;
        for (int i = 0; i < 2; i++) {
            if (textures[i]) delete textures[i];
        }
    }
    
    void onTouch(int x, int y, bool pressed) {
        touchX = x;
        touchY = y;
        touching = pressed;
        
        if (pressed) {
            if (btn_mode.rect.contains(x, y)) {
                display_mode = (display_mode == DISPLAY_MODE_1CH) ? 
                               DISPLAY_MODE_2CH : DISPLAY_MODE_1CH;
                active_count = (display_mode == DISPLAY_MODE_1CH) ? 1 : 2;
                active_streams[0] = true;
                active_streams[1] = (display_mode == DISPLAY_MODE_2CH);
                LOG("切换模式: %d路", active_count);
            }
        }
        update();
    }
    
    void processFrame() {
        for (int i = 0; i < active_count; i++) {
            SimFrame* frame = sim_cap->get_frame(i + 1);
            if (!frame) continue;
            
            FrameLatency lat;
            lat.frame_id = ++frame_id;
            lat.stream_id = i + 1;
            lat.kernel_ts = frame->ts;
            lat.capture_ts = get_ns();
            lat.rga_start_ts = lat.capture_ts + 500000;
            lat.rga_end_ts = lat.rga_start_ts + 1500000;
            lat.upload_start_ts = lat.rga_end_ts + 100000;
            lat.upload_end_ts = lat.upload_start_ts + 600000;
            lat.render_start_ts = lat.upload_end_ts + 200000;
            lat.render_end_ts = lat.render_start_ts + 1000000;
            lat.display_done_ts = lat.render_end_ts + 3000000;
            
            if (!textures[i]) {
                textures[i] = new QOpenGLTexture(QOpenGLTexture::Target2D);
                textures[i]->setSize(SDI_WIDTH, SDI_HEIGHT);
                textures[i]->setFormat(QOpenGLTexture::RGBA8_UNorm);
                textures[i]->allocateStorage();
            }
            textures[i]->setData(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, 
                                 frame->data);
            
            stats[i].add(lat);
            last_latency[i] = lat;
            
            sim_cap->release_frame(frame);
        }
        update();
    }
    
    StreamStats* getStats(int idx) { return &stats[idx]; }
    int getActiveCount() const { return active_count; }
    int getMode() const { return display_mode; }
    
protected:
    void paintGL() override {
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        
        painter.fillRect(0, 0, 1920, 1080, QColor(20, 20, 30));
        
        if (active_count == 1) {
            drawVideo(painter, 0, 0, 1920, 1080, 0);
            drawStatsPanel(painter, 1450, 60, 420, 400, 0);
        } else {
            drawVideo(painter, 0, 0, 940, 900, 0);
            drawVideo(painter, 980, 0, 940, 900, 1);
            drawStatsPanel(painter, 10, 920, 1900, 150, -1);
        }
        
        drawControls(painter);
        
        if (touching) {
            painter.setBrush(QColor(255, 255, 255, 180));
            painter.setPen(Qt::NoPen);
            painter.drawEllipse(touchX - 20, touchY - 20, 40, 40);
        }
    }

 禁用QT的触摸坐标。

一些参考

RK3588上跑QT的RTSP硬解方案:MPP解码稳定不崩,内存和句柄泄漏已清零_jam55-AMD开发者中国社区

此文作者采用了QOpenGLWidget 实现视频控件;同时用 QMainWindow 实现主窗口。这个是基本套路。相信作者可能直接在桌面环境实现或者后端用weston,或者单屏场景吧。

总结

  EGLFS 不支持两个QMainWindow

  又不支持两个QopenglWidget

 又不支持一个QMainWindows 中渲染两个屏幕。

  双屏异显时,QT 的触摸坐标映射也不对。

  以上5.12;5.15版本都这样。

限制太多了。

  如果所有功能是我们实现,则可以用 QOpenGLWindow实现双屏。而如果封装成widget供主程序使用则繁琐的多。

代码下载链接: https://pan.quark.cn/s/a4b39357ea24 用户账户控制(UAC)白名单的配置 Windows7环境中 UAC(User Account Control,用户帐户控制)是由微软在Windows Vista版本中推出的一项旨在增强系统安全性的创新技术,该技术强制要求用户在执行可能干扰计算机正常运作的操作或进行更改会波及其他用户设置的变动前,必须提供相应的权限或管理员密码进行验证。通过对这些操作启动前进行授权确认,UAC能够有效阻止恶意软件及间谍软件在未获授权的状态下于计算机内进行安装或实施修改。 自从Vista版本问世以来,微软便开始推行这一全新的安全机制,可视为对系统安全防护的显著提升。尽管UAC确实能够在一定程度上对某些非法程序起到防御作用,但与此同时,这一功能也给众多用户带来了诸多不便。 因此,许多用户开始探寻是否存在类似于白名单的功能,以便将那些值得信赖的程序直接赋予运行权限。事实上,这类功能确实存在,不过微软并未将其作为标准配置提供。 网络上关于此问题的绝大多数建议都是建议禁用UAC,这种说法显然缺乏针对性,因为若用户希望禁用此功能,本就不会提出相关疑问。 通过运用微软官方发布的Microsoft Application Compatibility Toolkit 5.6版本,可以将信任的程序纳入系统白名单范畴。 获取Application Compatibility Toolkit 安装程序成功后会出现三个可执行文件 以管理员身份启动Compatibility Administrator 在Custom DataBases部分创建新的数据库,并添加一个Application Fix(在下方空白处点击右键,选择...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

proware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值