目录
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供主程序使用则繁琐的多。

246

被折叠的 条评论
为什么被折叠?



