From a475a61aeee64326ab0d7424391d5803366c5182 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Wed, 25 Feb 2026 02:30:08 +0800 Subject: [PATCH 2/6] GL fixes --- src/qmplay2/CMakeLists.txt | 27 +++- src/qmplay2/opengl/OpenGLCommon.cpp | 53 +++++-- src/qmplay2/opengl/OpenGLInstance.cpp | 48 ++----- src/qmplay2/opengl/OpenGLWidget.cpp | 15 +- src/qmplay2/opengl/OpenGLWidget.hpp | 20 +-- src/qmplay2/opengl/OpenGLWidgetQt4.cpp | 85 +++++++++++ src/qmplay2/opengl/OpenGLWidgetQt4.hpp | 47 +++++++ src/qmplay2/opengl/OpenGLWindow.cpp | 81 +++++------ src/qmplay2/opengl/OpenGLWindow.hpp | 24 ++-- src/qmplay2/opengl/OpenGLWindowQt4.cpp | 187 +++++++++++++++++++++++++ src/qmplay2/opengl/OpenGLWindowQt4.hpp | 61 ++++++++ src/qmplay2/opengl/OpenGLWriter.cpp | 10 +- 12 files changed, 527 insertions(+), 131 deletions(-) create mode 100644 src/qmplay2/opengl/OpenGLWidgetQt4.cpp create mode 100644 src/qmplay2/opengl/OpenGLWidgetQt4.hpp create mode 100644 src/qmplay2/opengl/OpenGLWindowQt4.cpp create mode 100644 src/qmplay2/opengl/OpenGLWindowQt4.hpp diff --git a/src/qmplay2/CMakeLists.txt b/src/qmplay2/CMakeLists.txt index 7eacdcba..1da1822e 100644 --- a/src/qmplay2/CMakeLists.txt +++ b/src/qmplay2/CMakeLists.txt @@ -111,8 +111,6 @@ if(USE_OPENGL) opengl/OpenGLInstance.hpp opengl/OpenGLWriter.hpp opengl/OpenGLCommon.hpp - opengl/OpenGLWindow.hpp - opengl/OpenGLWidget.hpp opengl/OpenGLVertices.hpp opengl/OpenGLHWInterop.hpp ) @@ -120,11 +118,32 @@ if(USE_OPENGL) opengl/OpenGLInstance.cpp opengl/OpenGLWriter.cpp opengl/OpenGLCommon.cpp - opengl/OpenGLWindow.cpp - opengl/OpenGLWidget.cpp opengl/opengl.qrc ) + if(BUILD_WITH_QT4) + # Qt4 has no QOpenGLWidget/QOpenGLWindow (QWindow doesn't exist pre-Qt5), + # so OpenGLWidget/OpenGLWindow are built against QGLWidget instead, from + # separate Qt4-only files. This keeps the Qt5/Qt6 files untouched. + list(APPEND QMPLAY2_OPENGL_HDR + opengl/OpenGLWindowQt4.hpp + opengl/OpenGLWidgetQt4.hpp + ) + list(APPEND QMPLAY2_OPENGL_SRC + opengl/OpenGLWindowQt4.cpp + opengl/OpenGLWidgetQt4.cpp + ) + else() + list(APPEND QMPLAY2_OPENGL_HDR + opengl/OpenGLWindow.hpp + opengl/OpenGLWidget.hpp + ) + list(APPEND QMPLAY2_OPENGL_SRC + opengl/OpenGLWindow.cpp + opengl/OpenGLWidget.cpp + ) + endif() + # Qt4 has OpenGL support built into QtOpenGL (already linked via QT_USE_FILE) # For macOS 10.6.8, we use OpenGL 2.0 find_package(OpenGL REQUIRED) diff --git a/src/qmplay2/opengl/OpenGLCommon.cpp b/src/qmplay2/opengl/OpenGLCommon.cpp index 3ad1d289..c7fe3540 100644 --- a/src/qmplay2/opengl/OpenGLCommon.cpp +++ b/src/qmplay2/opengl/OpenGLCommon.cpp @@ -27,6 +27,9 @@ #include #include +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + #include +#endif #include #include #include @@ -56,6 +59,33 @@ #define GL_RED GL_RED_EXT #endif +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +// Qt4: only declares core GL 1.x entry points on macOS, so +// glMapBufferRange (GL_ARB_map_buffer_range / core since GL 3.0) has no +// prototype and isn't guaranteed to be a directly linkable symbol. Resolve it +// through QGLContext::getProcAddress(), the standard Qt4 mechanism for GL +// extension functions, instead of calling it as a directly-linked symbol. +// This only runs when m_glInstance->hasMapBufferRange is true (i.e. the +// extension was actually detected), so GL2.0-only hardware without it simply +// never reaches this code path (see OpenGLInstance.cpp's capability probe). +typedef GLvoid *(*QMPlay2MapBufferRangeFn)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +static QMPlay2MapBufferRangeFn qmplay2_glMapBufferRange = nullptr; +static bool qmplay2_glMapBufferRangeResolved = false; +static GLvoid *qmplay2_mapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) +{ + if (!qmplay2_glMapBufferRangeResolved) + { + qmplay2_glMapBufferRangeResolved = true; + if (auto ctx = QGLContext::currentContext()) + qmplay2_glMapBufferRange = reinterpret_cast(ctx->getProcAddress(QString("glMapBufferRange"))); + } + if (!qmplay2_glMapBufferRange) + return nullptr; + return qmplay2_glMapBufferRange(target, offset, length, access); +} + #define glMapBufferRange qmplay2_mapBufferRange +#endif + OpenGLCommon::OpenGLCommon() : VideoOutputCommon(false), vSync(true), @@ -77,9 +107,8 @@ OpenGLCommon::OpenGLCommon() : texCoordYCbCr[0] = texCoordYCbCr[4] = texCoordYCbCr[5] = texCoordYCbCr[7] = 0.0f; texCoordYCbCr[1] = texCoordYCbCr[3] = 1.0f; -#ifndef Q_OS_MAC + // Qt4+macOS may have OpenGL 2.0, not 3.0 - check the actual version m_gl3 = (m_glInstance->glVer >= 30); -#endif m_matrixChangeFn = [this] { setMatrix = true; @@ -270,6 +299,12 @@ void OpenGLCommon::initializeGL() if (target == GL_TEXTURE_RECTANGLE_ARB) videoFrag.prepend("#define TEXTURE_RECTANGLE\n"); shaderProgramVideo->addShaderFromSourceCode(QGLShader::Fragment, videoFrag); + if (!shaderProgramVideo->link()) + { + QMPlay2Core.logError(tr("Shader link error: ") + shaderProgramVideo->log()); + isOK = false; + return; + } if (shaderProgramVideo->bind()) { texCoordYCbCrLoc = shaderProgramVideo->attributeLocation("aTexCoord"); @@ -294,6 +329,12 @@ void OpenGLCommon::initializeGL() /* OSD shader */ shaderProgramOSD->addShaderFromSourceCode(QGLShader::Vertex, readShader(":/opengl/OSD.vert")); shaderProgramOSD->addShaderFromSourceCode(QGLShader::Fragment, readShader(":/opengl/OSD.frag")); + if (!shaderProgramOSD->link()) + { + QMPlay2Core.logError(tr("Shader link error: ") + shaderProgramOSD->log()); + isOK = false; + return; + } if (shaderProgramOSD->bind()) { texCoordOSDLoc = shaderProgramOSD->attributeLocation("aTexCoord"); @@ -451,12 +492,10 @@ void OpenGLCommon::paintGL() { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[p + 1]); quint8 *dst = nullptr; -#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2) - #if !defined(Q_OS_MAC) if (m_glInstance->hasMapBufferRange) dst = (quint8 *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, w * h * bytesMultiplier, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2) else - #endif dst = (quint8 *)::glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); #endif if (!dst) @@ -629,12 +668,10 @@ void OpenGLCommon::paintGL() if (hasNewSize) glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_DYNAMIC_DRAW); quint8 *dst = nullptr; -#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2) - #if !defined(Q_OS_MAC) if (m_glInstance->hasMapBufferRange) dst = (quint8 *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, dataSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#if !defined(QT_OPENGL_ES_2) && !defined(QT_FEATURE_opengles2) else - #endif dst = (quint8 *)::glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); #endif if (!dst) diff --git a/src/qmplay2/opengl/OpenGLInstance.cpp b/src/qmplay2/opengl/OpenGLInstance.cpp index 96102785..ffb9149c 100644 --- a/src/qmplay2/opengl/OpenGLInstance.cpp +++ b/src/qmplay2/opengl/OpenGLInstance.cpp @@ -10,45 +10,30 @@ bool OpenGLInstance::init() { - // Qt4: Need an active GL context to query - // Create a temporary context if needed -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - QGLWidget *tempWidget = nullptr; - const QGLContext *existingContext = QGLContext::currentContext(); - if (!existingContext) + // Qt4: no QOffscreenSurface/QOpenGLContext, so make sure a GL context is + // current by creating a temporary QGLWidget if one isn't already active. + // This lets us probe real driver/extension capabilities instead of + // hardcoding GL 2.0 defaults, so this also works correctly on newer + // macOS/drivers that support GL 2.1+ (PBOs, GL_ARB_map_buffer_range, etc.), + // not just the conservative macOS 10.6 baseline. + std::unique_ptr tempWidget; + if (!QGLContext::currentContext()) { - // Create temporary offscreen widget to get a context QGLFormat format; format.setVersion(2, 0); - tempWidget = new QGLWidget(format); - - // Ensure context is created - on some platforms we need this + tempWidget.reset(new QGLWidget(format)); if (!tempWidget->isValid()) - { - delete tempWidget; return false; - } tempWidget->makeCurrent(); - - // Verify context is now current if (!QGLContext::currentContext()) - { - delete tempWidget; return false; - } } -#endif // Get OpenGL version const char *versionStr = (const char *)glGetString(GL_VERSION); if (!versionStr) - { -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - delete tempWidget; -#endif return false; - } // Parse version string (e.g., "2.1 ..." or "3.0 ...") int majorVersion = 2; @@ -92,17 +77,10 @@ bool OpenGLInstance::init() ((majorVersion >= 2) || extensionSet.contains("GL_ARB_pixel_buffer_object")); } -#ifndef Q_OS_MAC - glVer = majorVersion * 10 + minorVersion; -#else - // macOS 10.6.8 has OpenGL 2.0/2.1 - glVer = 20; -#endif - -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - // Clean up temporary widget - delete tempWidget; -#endif + // Use the detected version rather than hardcoding 2.0, so a newer + // macOS/driver reporting 2.1+ (or 3.0+) is reflected accurately; falls + // back to 2.0 only if somehow the parsed version is below that. + glVer = (majorVersion >= 2) ? (majorVersion * 10 + minorVersion) : 20; return true; } diff --git a/src/qmplay2/opengl/OpenGLWidget.cpp b/src/qmplay2/opengl/OpenGLWidget.cpp index 84a945d2..f7a7da05 100644 --- a/src/qmplay2/opengl/OpenGLWidget.cpp +++ b/src/qmplay2/opengl/OpenGLWidget.cpp @@ -18,11 +18,12 @@ #include "OpenGLWidget.hpp" +#include + OpenGLWidget::OpenGLWidget() { m_widget = this; connect(&updateTimer, SIGNAL(timeout()), this, SLOT(update())); - connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant))); } OpenGLWidget::~OpenGLWidget() { @@ -47,7 +48,7 @@ void OpenGLWidget::setVSync(bool enable) Q_UNUSED(enable) // Not supported } -void OpenGLWidget::requestGLUpdate(bool requestDelayed) +void OpenGLWidget::updateGL(bool requestDelayed) { if (requestDelayed) QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); @@ -57,10 +58,7 @@ void OpenGLWidget::requestGLUpdate(bool requestDelayed) void OpenGLWidget::initializeGL() { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(aboutToBeDestroyed()), Qt::DirectConnection); -#endif - // Qt4: QGLContext doesn't emit aboutToBeDestroyed(), cleanup happens in destructor OpenGLCommon::initializeGL(); } void OpenGLWidget::paintGL() @@ -76,13 +74,8 @@ void OpenGLWidget::aboutToBeDestroyed() doneCurrent(); } -void OpenGLWidget::onRotValueUpdated(const QVariant &value) -{ - rotValueUpdated(value); -} - bool OpenGLWidget::event(QEvent *e) { dispatchEvent(e, parent()); - return QGLWidget::event(e); + return QOpenGLWidget::event(e); } diff --git a/src/qmplay2/opengl/OpenGLWidget.hpp b/src/qmplay2/opengl/OpenGLWidget.hpp index 4c41a5a4..43503855 100644 --- a/src/qmplay2/opengl/OpenGLWidget.hpp +++ b/src/qmplay2/opengl/OpenGLWidget.hpp @@ -20,26 +20,26 @@ #include "OpenGLCommon.hpp" -#include +#include +#include -class OpenGLWidget : public QGLWidget, public OpenGLCommon +class OpenGLWidget final : public QOpenGLWidget, public OpenGLCommon { Q_OBJECT public: OpenGLWidget(); ~OpenGLWidget(); - bool makeContextCurrent(); - void doneContextCurrent(); + bool makeContextCurrent() override; + void doneContextCurrent() override; - void setVSync(bool enable); - void requestGLUpdate(bool requestDelayed); + void setVSync(bool enable) override; + void updateGL(bool requestDelayed) override; - void initializeGL(); - void paintGL(); + void initializeGL() override; + void paintGL() override; private slots: void aboutToBeDestroyed(); - void onRotValueUpdated(const QVariant &value); private: - bool event(QEvent *e); + bool event(QEvent *e) override; }; diff --git a/src/qmplay2/opengl/OpenGLWidgetQt4.cpp b/src/qmplay2/opengl/OpenGLWidgetQt4.cpp new file mode 100644 index 00000000..b81fa6c7 --- /dev/null +++ b/src/qmplay2/opengl/OpenGLWidgetQt4.cpp @@ -0,0 +1,85 @@ +/* + QMPlay2 is a video and audio player. + Copyright (C) 2010-2026 Błażej Szczygieł + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "OpenGLWidgetQt4.hpp" + +OpenGLWidget::OpenGLWidget() +{ + m_widget = this; + connect(&updateTimer, SIGNAL(timeout()), this, SLOT(update())); + connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant))); +} +OpenGLWidget::~OpenGLWidget() +{ + makeCurrent(); +} + +bool OpenGLWidget::makeContextCurrent() +{ + if (!context()) + return false; + + makeCurrent(); + return true; +} +void OpenGLWidget::doneContextCurrent() +{ + doneCurrent(); +} + +void OpenGLWidget::setVSync(bool enable) +{ + Q_UNUSED(enable) + // Not supported +} +void OpenGLWidget::requestGLUpdate(bool requestDelayed) +{ + if (requestDelayed) + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + else + update(); +} + +void OpenGLWidget::initializeGL() +{ + // Qt4: QGLContext doesn't emit aboutToBeDestroyed(), cleanup happens in destructor + OpenGLCommon::initializeGL(); +} +void OpenGLWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT); + OpenGLCommon::paintGL(); +} + +void OpenGLWidget::aboutToBeDestroyed() +{ + makeCurrent(); + contextAboutToBeDestroyed(); + doneCurrent(); +} + +void OpenGLWidget::onRotValueUpdated(const QVariant &value) +{ + rotValueUpdated(value); +} + +bool OpenGLWidget::event(QEvent *e) +{ + dispatchEvent(e, parent()); + return QGLWidget::event(e); +} diff --git a/src/qmplay2/opengl/OpenGLWidgetQt4.hpp b/src/qmplay2/opengl/OpenGLWidgetQt4.hpp new file mode 100644 index 00000000..731f6667 --- /dev/null +++ b/src/qmplay2/opengl/OpenGLWidgetQt4.hpp @@ -0,0 +1,47 @@ +/* + QMPlay2 is a video and audio player. + Copyright (C) 2010-2026 Błażej Szczygieł + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include "OpenGLCommon.hpp" + +#include + +// Qt4: No QOpenGLWidget, use QGLWidget instead. Kept as a separate class/file +// from OpenGLWidget so Qt5/Qt6 builds are unaffected (see OpenGLWidget.hpp). +class OpenGLWidget : public QGLWidget, public OpenGLCommon +{ + Q_OBJECT +public: + OpenGLWidget(); + ~OpenGLWidget(); + + bool makeContextCurrent(); + void doneContextCurrent(); + + void setVSync(bool enable); + void requestGLUpdate(bool requestDelayed); + + void initializeGL(); + void paintGL(); +private slots: + void aboutToBeDestroyed(); + void onRotValueUpdated(const QVariant &value); +private: + bool event(QEvent *e); +}; diff --git a/src/qmplay2/opengl/OpenGLWindow.cpp b/src/qmplay2/opengl/OpenGLWindow.cpp index 1c9c4b78..b44399ac 100644 --- a/src/qmplay2/opengl/OpenGLWindow.cpp +++ b/src/qmplay2/opengl/OpenGLWindow.cpp @@ -20,37 +20,29 @@ #include +#include +#include #include OpenGLWindow::OpenGLWindow() - : m_platformName() + : m_platformName(QGuiApplication::platformName()) , m_passEventsToParent( -#ifdef Q_OS_MAC - true // macOS - pass events to parent -#elif defined(Q_OS_WIN) || defined(Q_OS_ANDROID) - false // Windows/Android - transparent input -#else - false // Linux/X11 - transparent input +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + !m_platformName.contains(QStringLiteral("wayland")) && + m_platformName != QStringLiteral("windows") && #endif - ) + m_platformName != QStringLiteral("xcb") && m_platformName != QStringLiteral("android")) { connect(&updateTimer, SIGNAL(timeout()), this, SLOT(doUpdateGL())); - connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant))); - - // Qt4: QGLWidget is already a widget, no need for container - m_widget = this; - -#if !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) - setAttribute(Qt::WA_NativeWindow); -#endif - - setAcceptDrops(false); if (!m_passEventsToParent) - { - // Make widget transparent to input events - setAttribute(Qt::WA_TransparentForMouseEvents); - } + setFlags(Qt::WindowTransparentForInput); + + m_widget = QWidget::createWindowContainer(this); + if (!m_platformName.contains(QStringLiteral("wayland")) && m_platformName != QStringLiteral("android")) + m_widget->setAttribute(Qt::WA_NativeWindow); + m_widget->installEventFilter(this); + m_widget->setAcceptDrops(false); connect(&QMPlay2Core, SIGNAL(videoDockVisible(bool)), this, SLOT(videoVisible(bool))); } @@ -79,35 +71,37 @@ void OpenGLWindow::doneContextCurrent() void OpenGLWindow::setVSync(bool enable) { - // Qt4: Use QGLFormat instead of QSurfaceFormat - QGLFormat fmt = format(); - if (enable != fmt.swapInterval()) + QSurfaceFormat fmt = format(); + if (!handle()) { - fmt.setSwapInterval(enable ? 1 : 0); - fmt.setDoubleBuffer(true); + fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); //Probably it doesn't work + fmt.setSwapInterval(enable); setFormat(fmt); - // Qt4: May need recreation for format changes - // But that can cause issues, so just set for next time + } + else if (enable != fmt.swapInterval()) + { + fmt.setSwapInterval(enable); + destroy(); + setFormat(fmt); + create(); + setVisible(true); } vSync = enable; } -void OpenGLWindow::requestGLUpdate(bool requestDelayed) +void OpenGLWindow::updateGL(bool requestDelayed) { - // Qt4: Use isVisible() instead of isExposed() - if (m_visible && isVisible()) + if (m_visible && isExposed()) QMetaObject::invokeMethod(this, "doUpdateGL", Qt::QueuedConnection, Q_ARG(bool, requestDelayed)); } void OpenGLWindow::initializeGL() { - // Qt4: QGLContext doesn't have aboutToBeDestroyed signal - // Context destruction is handled in destructor + connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(aboutToBeDestroyed()), Qt::DirectConnection); OpenGLCommon::initializeGL(); } void OpenGLWindow::paintGL() { - // Qt4: Use isVisible() instead of isExposed() - if (isVisible()) + if (isExposed()) { glClear(GL_COLOR_BUFFER_BIT); OpenGLCommon::paintGL(); @@ -136,15 +130,10 @@ void OpenGLWindow::videoVisible(bool v) m_visible = v && (m_widget->visibleRegion() != QRegion() || QMPlay2Core.getVideoDock()->visibleRegion() != QRegion()); } -void OpenGLWindow::onRotValueUpdated(const QVariant &value) -{ - rotValueUpdated(value); -} - bool OpenGLWindow::eventFilter(QObject *o, QEvent *e) { - // Qt4: m_widget is now this, so handle parent dispatching - dispatchEvent(e, parent()); + if (o == m_widget) + dispatchEvent(e, m_widget->parent()); return false; } @@ -158,9 +147,7 @@ bool OpenGLWindow::event(QEvent *e) case QEvent::MouseMove: case QEvent::FocusIn: case QEvent::FocusOut: -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) case QEvent::FocusAboutToChange: -#endif case QEvent::Enter: case QEvent::Leave: case QEvent::TabletMove: @@ -171,10 +158,8 @@ bool OpenGLWindow::event(QEvent *e) case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) case QEvent::InputMethodQuery: case QEvent::TouchCancel: -#endif if (m_passEventsToParent) return QCoreApplication::sendEvent(parent(), e); case QEvent::Wheel: @@ -183,5 +168,5 @@ bool OpenGLWindow::event(QEvent *e) default: break; } - return QGLWidget::event(e); + return QOpenGLWindow::event(e); } diff --git a/src/qmplay2/opengl/OpenGLWindow.hpp b/src/qmplay2/opengl/OpenGLWindow.hpp index 574da766..b61ab6e4 100644 --- a/src/qmplay2/opengl/OpenGLWindow.hpp +++ b/src/qmplay2/opengl/OpenGLWindow.hpp @@ -20,36 +20,34 @@ #include "OpenGLCommon.hpp" -#include +#include -// Qt4: No QOpenGLWindow, use QGLWidget -class OpenGLWindow : public QGLWidget, public OpenGLCommon +class OpenGLWindow final : private QOpenGLWindow, public OpenGLCommon { Q_OBJECT public: OpenGLWindow(); ~OpenGLWindow(); - void deleteMe(); + void deleteMe() override; - bool makeContextCurrent(); - void doneContextCurrent(); + bool makeContextCurrent() override; + void doneContextCurrent() override; - void setVSync(bool enable); - void requestGLUpdate(bool requestDelayed); + void setVSync(bool enable) override; + void updateGL(bool requestDelayed) override; - void initializeGL(); - void paintGL(); + void initializeGL() override; + void paintGL() override; private slots: void doUpdateGL(bool queued = false); void aboutToBeDestroyed(); void videoVisible(bool v); - void onRotValueUpdated(const QVariant &value); private: - bool eventFilter(QObject *o, QEvent *e); + bool eventFilter(QObject *o, QEvent *e) override; - bool event(QEvent *e); + bool event(QEvent *e) override; private: const QString m_platformName; diff --git a/src/qmplay2/opengl/OpenGLWindowQt4.cpp b/src/qmplay2/opengl/OpenGLWindowQt4.cpp new file mode 100644 index 00000000..6e3d86d2 --- /dev/null +++ b/src/qmplay2/opengl/OpenGLWindowQt4.cpp @@ -0,0 +1,187 @@ +/* + QMPlay2 is a video and audio player. + Copyright (C) 2010-2026 Błażej Szczygieł + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "OpenGLWindowQt4.hpp" + +#include + +#include + +OpenGLWindow::OpenGLWindow() + : m_platformName() + , m_passEventsToParent( +#ifdef Q_OS_MAC + true // macOS - pass events to parent +#elif defined(Q_OS_WIN) || defined(Q_OS_ANDROID) + false // Windows/Android - transparent input +#else + false // Linux/X11 - transparent input +#endif + ) +{ + connect(&updateTimer, SIGNAL(timeout()), this, SLOT(doUpdateGL())); + connect(&m_rotAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(onRotValueUpdated(QVariant))); + + // Qt4: QGLWidget is already a widget, no need for container + m_widget = this; + +#if !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) + setAttribute(Qt::WA_NativeWindow); +#endif + + setAcceptDrops(false); + + if (!m_passEventsToParent) + { + // Make widget transparent to input events + setAttribute(Qt::WA_TransparentForMouseEvents); + } + + connect(&QMPlay2Core, SIGNAL(videoDockVisible(bool)), this, SLOT(videoVisible(bool))); +} +OpenGLWindow::~OpenGLWindow() +{ + makeCurrent(); +} + +void OpenGLWindow::deleteMe() +{ + delete m_widget; +} + +bool OpenGLWindow::makeContextCurrent() +{ + if (!context()) + return false; + + makeCurrent(); + return true; +} +void OpenGLWindow::doneContextCurrent() +{ + doneCurrent(); +} + +void OpenGLWindow::setVSync(bool enable) +{ + // Qt4: Use QGLFormat instead of QSurfaceFormat + QGLFormat fmt = format(); + if (enable != fmt.swapInterval()) + { + fmt.setSwapInterval(enable ? 1 : 0); + fmt.setDoubleBuffer(true); + setFormat(fmt); + // Qt4: May need recreation for format changes + // But that can cause issues, so just set for next time + } + vSync = enable; +} +void OpenGLWindow::requestGLUpdate(bool requestDelayed) +{ + // Qt4: Use isVisible() instead of isExposed() + if (m_visible && isVisible()) + QMetaObject::invokeMethod(this, "doUpdateGL", Qt::QueuedConnection, Q_ARG(bool, requestDelayed)); +} + +void OpenGLWindow::initializeGL() +{ + // Qt4: QGLContext doesn't have aboutToBeDestroyed signal + // Context destruction is handled in destructor + OpenGLCommon::initializeGL(); +} +void OpenGLWindow::paintGL() +{ + // Qt4: Use isVisible() instead of isExposed() + if (isVisible()) + { + glClear(GL_COLOR_BUFFER_BIT); + OpenGLCommon::paintGL(); + } +} + +void OpenGLWindow::doUpdateGL(bool queued) +{ + if (queued) + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); + else + { + //sendEvent() doesn't enqueue the event here + QEvent updateEvent(QEvent::UpdateRequest); + QCoreApplication::sendEvent(this, &updateEvent); + } +} +void OpenGLWindow::aboutToBeDestroyed() +{ + makeCurrent(); + contextAboutToBeDestroyed(); + doneCurrent(); +} +void OpenGLWindow::videoVisible(bool v) +{ + m_visible = v && (m_widget->visibleRegion() != QRegion() || QMPlay2Core.getVideoDock()->visibleRegion() != QRegion()); +} + +void OpenGLWindow::onRotValueUpdated(const QVariant &value) +{ + rotValueUpdated(value); +} + +bool OpenGLWindow::eventFilter(QObject *o, QEvent *e) +{ + // Qt4: m_widget is now this, so handle parent dispatching + dispatchEvent(e, parent()); + return false; +} + +bool OpenGLWindow::event(QEvent *e) +{ + switch (e->type()) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::FocusIn: + case QEvent::FocusOut: +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + case QEvent::FocusAboutToChange: +#endif + case QEvent::Enter: + case QEvent::Leave: + case QEvent::TabletMove: + case QEvent::TabletPress: + case QEvent::TabletRelease: + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + case QEvent::InputMethodQuery: + case QEvent::TouchCancel: +#endif + if (m_passEventsToParent) + return QCoreApplication::sendEvent(parent(), e); + case QEvent::Wheel: + if (m_passEventsToParent) + return QCoreApplication::sendEvent(const_cast(QMPlay2Core.getVideoDock()), e); + default: + break; + } + return QGLWidget::event(e); +} diff --git a/src/qmplay2/opengl/OpenGLWindowQt4.hpp b/src/qmplay2/opengl/OpenGLWindowQt4.hpp new file mode 100644 index 00000000..bfd45aa4 --- /dev/null +++ b/src/qmplay2/opengl/OpenGLWindowQt4.hpp @@ -0,0 +1,61 @@ +/* + QMPlay2 is a video and audio player. + Copyright (C) 2010-2026 Błażej Szczygieł + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include "OpenGLCommon.hpp" + +#include + +// Qt4: No QOpenGLWindow, use QGLWidget. Kept as a separate class/file from +// OpenGLWindow so Qt5/Qt6 builds are unaffected (see OpenGLWindow.hpp). +class OpenGLWindow : public QGLWidget, public OpenGLCommon +{ + Q_OBJECT +public: + OpenGLWindow(); + ~OpenGLWindow(); + + void deleteMe(); + + bool makeContextCurrent(); + void doneContextCurrent(); + + void setVSync(bool enable); + void requestGLUpdate(bool requestDelayed); + + void initializeGL(); + void paintGL(); + +private slots: + void doUpdateGL(bool queued = false); + void aboutToBeDestroyed(); + void videoVisible(bool v); + void onRotValueUpdated(const QVariant &value); +private: + bool eventFilter(QObject *o, QEvent *e); + + bool event(QEvent *e); + +private: + const QString m_platformName; + + const bool m_passEventsToParent; + + bool m_visible = true; +}; diff --git a/src/qmplay2/opengl/OpenGLWriter.cpp b/src/qmplay2/opengl/OpenGLWriter.cpp index b9e35e96..01113883 100644 --- a/src/qmplay2/opengl/OpenGLWriter.cpp +++ b/src/qmplay2/opengl/OpenGLWriter.cpp @@ -18,8 +18,14 @@ #include "OpenGLWriter.hpp" -#include "OpenGLWindow.hpp" -#include "OpenGLWidget.hpp" +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + #include "OpenGLWindow.hpp" + #include "OpenGLWidget.hpp" +#else + // Qt4: OpenGLWidget/OpenGLWindow need QGLWidget instead of QOpenGLWidget/QOpenGLWindow + #include "OpenGLWindowQt4.hpp" + #include "OpenGLWidgetQt4.hpp" +#endif #include "OpenGLHWInterop.hpp" #include