f8d415afbe
This hooks the platform specific events in order to close the obs display more accurately. Earlier attempts on hooking visiblity, but Qt does not ensure that visibility is changed before the surface is destroyed. So we ended up racing with the EGL driver and on some drivers if you lose the race they hang. Also only force display creation if the display is actually visible. When a source type is not video/drawable (or is missing) this would force the display to be created for the blank window and also hang. Finally force closure of the preview displays during scene cleanup to avoid similar ordering issues in Qt. Qt has even less order guarentees during close and we are sure that displays are no longer needed at this point in the UI.
243 lines
4.9 KiB
C++
243 lines
4.9 KiB
C++
#include "qt-display.hpp"
|
|
#include "qt-wrappers.hpp"
|
|
#include "display-helpers.hpp"
|
|
#include <QWindow>
|
|
#include <QScreen>
|
|
#include <QResizeEvent>
|
|
#include <QShowEvent>
|
|
|
|
#include <obs-config.h>
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
#ifdef ENABLE_WAYLAND
|
|
#include <obs-nix-platform.h>
|
|
|
|
class SurfaceEventFilter : public QObject {
|
|
OBSQTDisplay *display;
|
|
int mTimerId;
|
|
|
|
public:
|
|
SurfaceEventFilter(OBSQTDisplay *src) : display(src), mTimerId(0) {}
|
|
|
|
protected:
|
|
bool eventFilter(QObject *obj, QEvent *event) override
|
|
{
|
|
bool result = QObject::eventFilter(obj, event);
|
|
QPlatformSurfaceEvent *surfaceEvent;
|
|
|
|
switch (event->type()) {
|
|
case QEvent::PlatformSurface:
|
|
surfaceEvent =
|
|
static_cast<QPlatformSurfaceEvent *>(event);
|
|
if (surfaceEvent->surfaceEventType() !=
|
|
QPlatformSurfaceEvent::SurfaceCreated)
|
|
return result;
|
|
|
|
if (display->windowHandle()->isExposed())
|
|
createOBSDisplay();
|
|
else
|
|
mTimerId = startTimer(67); // Arbitrary
|
|
break;
|
|
case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed:
|
|
display->DestroyDisplay();
|
|
break;
|
|
case QEvent::Expose:
|
|
createOBSDisplay();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void timerEvent(QTimerEvent *)
|
|
{
|
|
createOBSDisplay(display->isVisible());
|
|
}
|
|
|
|
private:
|
|
void createOBSDisplay(bool force = false)
|
|
{
|
|
display->CreateDisplay(force);
|
|
if (mTimerId > 0) {
|
|
killTimer(mTimerId);
|
|
mTimerId = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
static inline long long color_to_int(const QColor &color)
|
|
{
|
|
auto shift = [&](unsigned val, int shift) {
|
|
return ((val & 0xff) << shift);
|
|
};
|
|
|
|
return shift(color.red(), 0) | shift(color.green(), 8) |
|
|
shift(color.blue(), 16) | shift(color.alpha(), 24);
|
|
}
|
|
|
|
static inline QColor rgba_to_color(uint32_t rgba)
|
|
{
|
|
return QColor::fromRgb(rgba & 0xFF, (rgba >> 8) & 0xFF,
|
|
(rgba >> 16) & 0xFF, (rgba >> 24) & 0xFF);
|
|
}
|
|
|
|
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) {
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
display = nullptr;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (!display) {
|
|
CreateDisplay();
|
|
} else {
|
|
QSize size = GetPixelSize(this);
|
|
obs_display_resize(display, size.width(),
|
|
size.height());
|
|
}
|
|
};
|
|
|
|
auto screenChanged = [this](QScreen *) {
|
|
CreateDisplay();
|
|
|
|
QSize size = GetPixelSize(this);
|
|
obs_display_resize(display, size.width(), size.height());
|
|
};
|
|
|
|
connect(windowHandle(), &QWindow::visibleChanged, windowVisible);
|
|
connect(windowHandle(), &QWindow::screenChanged, screenChanged);
|
|
|
|
#ifdef ENABLE_WAYLAND
|
|
if (obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND)
|
|
windowHandle()->installEventFilter(
|
|
new SurfaceEventFilter(this));
|
|
#endif
|
|
}
|
|
|
|
QColor OBSQTDisplay::GetDisplayBackgroundColor() const
|
|
{
|
|
return rgba_to_color(backgroundColor);
|
|
}
|
|
|
|
void OBSQTDisplay::SetDisplayBackgroundColor(const QColor &color)
|
|
{
|
|
uint32_t newBackgroundColor = (uint32_t)color_to_int(color);
|
|
|
|
if (newBackgroundColor != backgroundColor) {
|
|
backgroundColor = newBackgroundColor;
|
|
UpdateDisplayBackgroundColor();
|
|
}
|
|
}
|
|
|
|
void OBSQTDisplay::UpdateDisplayBackgroundColor()
|
|
{
|
|
obs_display_set_background_color(display, backgroundColor);
|
|
}
|
|
|
|
void OBSQTDisplay::CreateDisplay(bool force)
|
|
{
|
|
if (display)
|
|
return;
|
|
|
|
if (!windowHandle()->isExposed() && !force)
|
|
return;
|
|
|
|
QSize size = GetPixelSize(this);
|
|
|
|
gs_init_data info = {};
|
|
info.cx = size.width();
|
|
info.cy = size.height();
|
|
info.format = GS_BGRA;
|
|
info.zsformat = GS_ZS_NONE;
|
|
|
|
if (!QTToGSWindow(windowHandle(), info.window))
|
|
return;
|
|
|
|
display = obs_display_create(&info, backgroundColor);
|
|
|
|
emit DisplayCreated(this);
|
|
}
|
|
|
|
void OBSQTDisplay::paintEvent(QPaintEvent *event)
|
|
{
|
|
CreateDisplay();
|
|
|
|
QWidget::paintEvent(event);
|
|
}
|
|
|
|
void OBSQTDisplay::moveEvent(QMoveEvent *event)
|
|
{
|
|
QWidget::moveEvent(event);
|
|
|
|
OnMove();
|
|
}
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
bool OBSQTDisplay::nativeEvent(const QByteArray &, void *message, qintptr *)
|
|
#else
|
|
bool OBSQTDisplay::nativeEvent(const QByteArray &, void *message, long *)
|
|
#endif
|
|
{
|
|
#ifdef _WIN32
|
|
const MSG &msg = *static_cast<MSG *>(message);
|
|
switch (msg.message) {
|
|
case WM_DISPLAYCHANGE:
|
|
OnDisplayChange();
|
|
}
|
|
#else
|
|
UNUSED_PARAMETER(message);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
QPaintEngine *OBSQTDisplay::paintEngine() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void OBSQTDisplay::OnMove()
|
|
{
|
|
if (display)
|
|
obs_display_update_color_space(display);
|
|
}
|
|
|
|
void OBSQTDisplay::OnDisplayChange()
|
|
{
|
|
if (display)
|
|
obs_display_update_color_space(display);
|
|
}
|