QT 自定义渲染与OBS中的实现

本文介绍了如何在Qt中绕过默认的QPaintEngine,通过自定义QWidget类重写paintEvent和resizeEvent,同时讨论了Qt::WA_PaintOnScreen、Qt::NativeWindow属性以及OBSQTDisplay示例中利用OpenGL或Direct3D在QWidget上的渲染。
摘要由CSDN通过智能技术生成

QPaintEngine *QWidget::paintEngine 的实现:

/*!
    \fn QPaintEngine *QWidget::paintEngine() const

    Returns the widget's paint engine.

    Note that this function should not be called explicitly by the
    user, since it's meant for reimplementation purposes only. The
    function is called by Qt internally, and the default
    implementation may not always return a valid pointer.
*/
QPaintEngine *QWidget::paintEngine() const
{
    qWarning("QWidget::paintEngine: Should no longer be called");

#ifdef Q_OS_WIN
    // We set this bit which is checked in setAttribute for
    // Qt::WA_PaintOnScreen. We do this to allow these two scenarios:
    //
    // 1. Users accidentally set Qt::WA_PaintOnScreen on X and port to
    // Windows which would mean suddenly their widgets stop working.
    //
    // 2. Users set paint on screen and subclass paintEngine() to
    // return 0, in which case we have a "hole" in the backingstore
    // allowing use of GDI or DirectX directly.
    //
    // 1 is WRONG, but to minimize silent failures, we have set this
    // bit to ignore the setAttribute call. 2. needs to be
    // supported because its our only means of embedding native
    // graphics stuff.
    const_cast<QWidgetPrivate *>(d_func())->noPaintOnScreen = 1;
#endif

    return nullptr; //##### @@@
}

如果想不走qt 的渲染逻辑,那么需要做:

创建一个继承QWidget的类,重写paintEvent,resizeEvent
但是仅仅这样还不够,画面会一直闪烁,需要重写以下函数
virtual QPaintEngine paintEngine() const { return NULL; }*
并设置窗口属性
this->setAttribute(Qt::WA_PaintOnScreen, true);
this->setAttribute(Qt::WA_NativeWindow, true);

关于QT nativeWindow

1.系统级别 QApplication::setAttribute(Qt::AA_NativeWindows)
(1)设置Qt::AA_NativeWindows
所有控件均视为window,可通过QGuiApplication::allWindows()获取所有控件,然后获取窗口id和大小,并且,观察每个子widget大小(通过widget->geometry()获取),可发现均集中在屏幕左上角,并且在显示上,屏幕左上角均是矩形空白,并且随着缩放,矩形空白也在变化,widget大小也在变化,即整个widget是多个子widget的合并,每个widget在底层均有一个窗口系统的句柄和内存空间,对刷新会有一定影响。
对于依托父widget建立的widget,也有自己的窗口句柄,并且依然依附于父widget。
对于没有依托父widget建立的widget,有自己的窗口句柄,并且作为独立窗口,默认显示在屏幕左上角。

(2)不设置 Qt::AA_NativeWindows
只有toplevelWidget有本地窗口系统句柄,屏幕没有矩形空白,按照QT文档所述,非native_window由QT自己绘制。
对于依托父widget建立的widget,没有自己的窗口句柄,依附于父widget。
对于没有依托父widget建立的widget,有自己的窗口句柄,并且作为独立窗口,默认显示在屏幕左上角。

2.widget级别 setAttribute(Qt::WA_NativeWindow)
这个widget以及其祖先都会设置成native_window,并且父widget的所有子widget也都会变(enforceNativeChildren()函数),所以发现allWindows变多了,如果不需要所有祖先都变,需要加 setAttribute(Qt::WA_DontCreateNativeAncestors),如果不需要所有兄弟都变,需要加系统级参数QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);

3.widget的winId()函数和setAttribute(Qt::WA_NativeWindow)类似,并且返回widget的句柄id
 

如下面的qt-display中,设置了

	setAttribute(Qt::WA_DontCreateNativeAncestors);
	setAttribute(Qt::WA_NativeWindow);   
让自己的窗口成为native 同时禁止父窗口成为native窗口

同时设置了Qt::WA_PaintOnScreen属性:Qt Namespace | Qt Core 6.6.1

指示小部件想要直接绘制到屏幕上。具有此属性集的小部件不参与组合管理,
即它们不能是半透明的或通过半透明重叠小部件发光。注意:此标志仅在 X11
 上受支持,并且它禁用双缓冲。在 Qt for Embedded Linux 上,该标志
仅在顶级小部件上设置时才起作用,并且它依赖于活动屏幕驱动程序的支持。
该标志由小部件的作者设置或清除。要在 Qt 的绘画系统之外进行渲染,
例如,如果您需要本机绘画基元,则需要重新实现QWidget::paintEngine()
 返回 0 并设置此标志。

    setAttribute(Qt::WA_NoSystemBackground);
    setAttribute(Qt::WA_OpaquePaintEvent);

https://github.com/alibaba/tblive/blob/master/obs/qt-display.cpp

#pragma once

#include <QWidget>
#include <obs.hpp>

class OBSQTDisplay : public QWidget {
	Q_OBJECT

	OBSDisplay display;

	void CreateDisplay();

	void resizeEvent(QResizeEvent *event) override;
	void paintEvent(QPaintEvent *event) override;

signals:
	void DisplayCreated(OBSQTDisplay *window);
	void DisplayResized();

public:
	OBSQTDisplay(QWidget *parent = 0, Qt::WindowFlags flags = 0);

	virtual QPaintEngine *paintEngine() const override;

	inline obs_display_t *GetDisplay() const {return display;}
};
#include "qt-display.hpp"
#include "qt-wrappers.hpp"
#include "display-helpers.hpp"
#include <QWindow>
#include <QScreen>
#include <QResizeEvent>
#include <QShowEvent>

OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags)
	: QWidget(parent, flags)
{
	setAttribute(Qt::WA_PaintOnScreen);
	setAttribute(Qt::WA_StaticContents);
	setAttribute(Qt::WA_NoSystemBackground);
	setAttribute(Qt::WA_OpaquePaintEvent);
	setAttribute(Qt::WA_DontCreateNativeAncestors);
	setAttribute(Qt::WA_NativeWindow);

	auto windowVisible = [this] (bool visible)
	{
		if (!visible)
			return;

		if (!display) {
			CreateDisplay();
		} else {
			QSize size = GetPixelSize(this);
			obs_display_resize(display, size.width(), size.height());
		}
	};

	auto sizeChanged = [this] (QScreen*)
	{
		CreateDisplay();

		QSize size = GetPixelSize(this);
		obs_display_resize(display, size.width(), size.height());
	};

	connect(windowHandle(), &QWindow::visibleChanged, windowVisible);
	connect(windowHandle(), &QWindow::screenChanged, sizeChanged);
}

void OBSQTDisplay::CreateDisplay()
{
	if (display || !windowHandle()->isExposed())
		return;

	QSize size = GetPixelSize(this);

	gs_init_data info      = {};
	info.cx                = size.width();
	info.cy                = size.height();
	info.format            = GS_RGBA;
	info.zsformat          = GS_ZS_NONE;

    //将qt的创建句柄
	QTToGSWindow(winId(), info.window);

	display = obs_display_create(&info);

	emit DisplayCreated(this);
}

void OBSQTDisplay::resizeEvent(QResizeEvent *event)
{
	QWidget::resizeEvent(event);

	CreateDisplay();

	if (isVisible() && display) {
		QSize size = GetPixelSize(this);
		obs_display_resize(display, size.width(), size.height());
	}

	emit DisplayResized();
}

void OBSQTDisplay::paintEvent(QPaintEvent *event)
{
	CreateDisplay();

	QWidget::paintEvent(event);
}

QPaintEngine *OBSQTDisplay::paintEngine() const
{
	return nullptr;
}

如obs中使用opengl在qwidget上绘制如上,使用如下

OBS源码使用学习(二)之预览框显示_obsqtdisplay-CSDN博客

OBS中的预览框:

【obs-studio开源项目从入门到放弃】预览窗口中source的UI操作绘制处理 - 代码先锋网

其他:

从零开始开发3D游戏引擎 - 在QtWidget中进行Direct3D11渲染,并捕捉Windows消息_qt5窗口显示d3d11-CSDN博客

QT调用GDI绘图-CSDN博客

OBS:

 主界面:OBSBasic::OBSBasic

预览界面:OBSBasicPreview

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值