diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bc9399d9b3a..d25108bb313c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,11 @@ if(NOT CMAKE_CONFIGURATION_TYPES) endif() option(QT6 "Build with Qt6" OFF) +option(QOPENGL "Use QOpenGL... classes instead of QGLWidget" OFF) + +if(QOPENGL) + add_compile_definitions(MIXXX_USE_QOPENGL) +endif() if(APPLE) if(QT6) @@ -1113,7 +1118,6 @@ else() src/waveform/renderers/waveformrendermarkrange.cpp src/waveform/renderers/waveformsignalcolors.cpp src/waveform/renderers/waveformwidgetrenderer.cpp - src/waveform/sharedglcontext.cpp src/waveform/visualsmanager.cpp src/waveform/vsyncthread.cpp src/waveform/waveformmarklabel.cpp @@ -1133,16 +1137,29 @@ else() src/waveform/widgets/rgbwaveformwidget.cpp src/waveform/widgets/softwarewaveformwidget.cpp src/waveform/widgets/waveformwidgetabstract.cpp - src/widget/wglwidgetqglwidget.cpp src/widget/woverview.cpp src/widget/woverviewhsv.cpp src/widget/woverviewlmh.cpp src/widget/woverviewrgb.cpp src/widget/wspinny.cpp - src/widget/wvumetergl.cpp src/widget/wwaveformviewer.cpp ) endif() + +if(QOPENGL) + target_sources(mixxx-lib PRIVATE + src/widget/openglwindow.cpp + src/widget/wglwidgetqopengl.cpp + src/widget/wvumeterglqopengl.cpp + ) +else() + target_sources(mixxx-lib PRIVATE + src/waveform/sharedglcontext.cpp + src/widget/wglwidgetqglwidget.cpp + src/widget/wvumeterglqgl.cpp + ) +endif() + set_target_properties(mixxx-lib PROPERTIES AUTOMOC ON AUTOUIC ON CXX_CLANG_TIDY "${CLANG_TIDY}") target_include_directories(mixxx-lib PUBLIC src "${CMAKE_CURRENT_BINARY_DIR}/src") if(UNIX AND NOT APPLE) diff --git a/src/main.cpp b/src/main.cpp index 6bb4574d0660..a28350e6d198 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -122,6 +122,9 @@ int main(int argc, char * argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif +#ifdef MIXXX_USE_QOPENGL + QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); +#endif // workaround for https://bugreports.qt.io/browse/QTBUG-84363 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index b5a6b8e3fe44..15b1319bab34 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -2,7 +2,11 @@ #include #include + +#ifndef MIXXX_USE_QOPENGL #include +#endif + #include #include @@ -152,6 +156,7 @@ void MixxxMainWindow::initialize() { } }); +#ifndef MIXXX_USE_QOPENGL // Before creating the first skin we need to create a QGLWidget so that all // the QGLWidget's we create can use it as a shared QGLContext. if (!CmdlineArgs::Instance().getSafeMode() && QGLFormat::hasOpenGL()) { @@ -181,6 +186,7 @@ void MixxxMainWindow::initialize() { pContextWidget->hide(); SharedGLContext::setWidget(pContextWidget); } +#endif WaveformWidgetFactory::createInstance(); // takes a long time WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index 9eb1b26f4272..2f4d6cfc447e 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1353,7 +1353,7 @@ QWidget* LegacySkinParser::parseVuMeter(const QDomElement& node) { return pVuMeterWidget; } - // QGLWidget derived WVuMeterGL + // WGLWidget derived WVuMeterGL if (CmdlineArgs::Instance().getSafeMode()) { WLabel* dummy = new WLabel(m_pParent); diff --git a/src/util/widgetrendertimer.cpp b/src/util/widgetrendertimer.cpp index 43b51a40f0a5..1b102de60684 100644 --- a/src/util/widgetrendertimer.cpp +++ b/src/util/widgetrendertimer.cpp @@ -1,5 +1,7 @@ #include "util/widgetrendertimer.h" +#ifdef USE_WIDGET_RENDER_TIMER + #include "moc_widgetrendertimer.cpp" #include "util/time.h" @@ -28,3 +30,5 @@ void WidgetRenderTimer::activity() { m_guiTickTimer.start(m_renderFrequency); } } + +#endif diff --git a/src/util/widgetrendertimer.h b/src/util/widgetrendertimer.h index 25fc2f3cc8ae..7fec8ccedd5a 100644 --- a/src/util/widgetrendertimer.h +++ b/src/util/widgetrendertimer.h @@ -26,6 +26,16 @@ // Ironically, using this class somehow causes severe lagginess on mouse input // with Windows, so use #ifdefs to only call activity() on macOS; just call // QWidget::update() for other operating systems. +// +// Also when using the QOpenGLWindow based WGLWidget (when MIXXX_USE_QOPENGL is +// defined) using this seems not necessary and makes causes lagginess +#ifdef __APPLE__ +#ifndef MIXXX_USE_QOPENGL +#define USE_WIDGET_RENDER_TIMER +#endif +#endif + +#ifdef USE_WIDGET_RENDER_TIMER class WidgetRenderTimer : public QObject { Q_OBJECT public: @@ -53,3 +63,4 @@ class WidgetRenderTimer : public QObject { mixxx::Duration m_lastActivity; mixxx::Duration m_lastRender; }; +#endif diff --git a/src/waveform/renderers/glslwaveformrenderersignal.cpp b/src/waveform/renderers/glslwaveformrenderersignal.cpp index 5b0f33a6687f..078401b2d4c6 100644 --- a/src/waveform/renderers/glslwaveformrenderersignal.cpp +++ b/src/waveform/renderers/glslwaveformrenderersignal.cpp @@ -1,6 +1,11 @@ #include "waveform/renderers/glslwaveformrenderersignal.h" #if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2) +#ifdef MIXXX_USE_QOPENGL +#include +#include +#else +#endif #include #include @@ -49,14 +54,16 @@ bool GLSLWaveformRendererSignal::loadShaders() { m_frameShaderProgram->removeAllShaders(); if (!m_frameShaderProgram->addShaderFromSourceFile( - QGLShader::Vertex, ":/shaders/passthrough.vert")) { + GL_SHADER_CLASS::Vertex, + ":/shaders/passthrough.vert")) { qDebug() << "GLWaveformRendererSignalShader::loadShaders - " << m_frameShaderProgram->log(); return false; } if (!m_frameShaderProgram->addShaderFromSourceFile( - QGLShader::Fragment, m_pFragShader)) { + GL_SHADER_CLASS::Fragment, + m_pFragShader)) { qDebug() << "GLWaveformRendererSignalShader::loadShaders - " << m_frameShaderProgram->log(); return false; @@ -182,8 +189,8 @@ void GLSLWaveformRendererSignal::createFrameBuffers() { static_cast( m_waveformRenderer->getHeight() * devicePixelRatio); - m_framebuffer = std::make_unique(bufferWidth, - bufferHeight); + m_framebuffer = std::make_unique(bufferWidth, + bufferHeight); if (!m_framebuffer->isValid()) { qWarning() << "GLSLWaveformRendererSignal::createFrameBuffer - frame buffer not valid"; @@ -195,7 +202,7 @@ void GLSLWaveformRendererSignal::onInitializeGL() { m_textureRenderedWaveformCompletion = 0; if (!m_frameShaderProgram) { - m_frameShaderProgram = std::make_unique(); + m_frameShaderProgram = std::make_unique(); } if (!loadShaders()) { diff --git a/src/waveform/renderers/glslwaveformrenderersignal.h b/src/waveform/renderers/glslwaveformrenderersignal.h index b56090ddcc2c..042919756bf4 100644 --- a/src/waveform/renderers/glslwaveformrenderersignal.h +++ b/src/waveform/renderers/glslwaveformrenderersignal.h @@ -7,8 +7,18 @@ #include "util/memory.h" #include "waveform/renderers/waveformrenderersignalbase.h" -QT_FORWARD_DECLARE_CLASS(QGLFramebufferObject) -QT_FORWARD_DECLARE_CLASS(QGLShaderProgram) +#ifdef MIXXX_USE_QOPENGL +#define GL_FBO_CLASS QOpenGLFramebufferObject +#define GL_SHADER_CLASS QOpenGLShader +#define GL_SHADER_PROGRAM_CLASS QOpenGLShaderProgram +#else +#define GL_FBO_CLASS QGLFramebufferObject +#define GL_SHADER_CLASS QGLShader +#define GL_SHADER_PROGRAM_CLASS QGLShaderProgram +#endif + +QT_FORWARD_DECLARE_CLASS(GL_FBO_CLASS) +QT_FORWARD_DECLARE_CLASS(GL_SHADER_PROGRAM_CLASS) class GLSLWaveformRendererSignal : public QObject, public WaveformRendererSignalBase, @@ -51,7 +61,7 @@ class GLSLWaveformRendererSignal : public QObject, int m_textureRenderedWaveformCompletion; // Frame buffer for two pass rendering. - std::unique_ptr m_framebuffer; + std::unique_ptr m_framebuffer; bool m_bDumpPng; @@ -59,7 +69,7 @@ class GLSLWaveformRendererSignal : public QObject, bool m_shadersValid; ColorType m_colorType; const QString m_pFragShader; - std::unique_ptr m_frameShaderProgram; + std::unique_ptr m_frameShaderProgram; }; class GLSLWaveformRendererFilteredSignal: public GLSLWaveformRendererSignal { diff --git a/src/waveform/renderers/glwaveformrenderer.h b/src/waveform/renderers/glwaveformrenderer.h index 141ceb6cf1e9..3bfb2a91f91c 100644 --- a/src/waveform/renderers/glwaveformrenderer.h +++ b/src/waveform/renderers/glwaveformrenderer.h @@ -1,6 +1,5 @@ #pragma once -#include #include #if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2) @@ -10,6 +9,28 @@ /// Note that the Qt OpenGL WaveformRendererAbstracts are not GLWaveformRenderers because /// they do not call OpenGL functions directly. Instead, they inherit QGLWidget and use the /// QPainter API which Qt translates to OpenGL under the hood. + +#ifdef MIXXX_USE_OPENGL + +class GLWaveformRenderer : protected QOpenGLFunctions_2_1 { + public: + GLWaveformRenderer() { + } + + virtual void onInitializeGL() { + initializeOpenGLFunctions(); + } + // Not needed when using QOpenGL classes, + // leaving empty function so calling code + // can be unchanged + void maybeInitializeGL() { + } +}; + +#else + +#include + class GLWaveformRenderer : protected QOpenGLFunctions_2_1 { public: GLWaveformRenderer() @@ -35,4 +56,6 @@ class GLWaveformRenderer : protected QOpenGLFunctions_2_1 { const QGLContext* m_pLastContext; }; +#endif + #endif // !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2) diff --git a/src/waveform/renderers/waveformrenderbeat.cpp b/src/waveform/renderers/waveformrenderbeat.cpp index b4bc76ab9061..53ac55b88a28 100644 --- a/src/waveform/renderers/waveformrenderbeat.cpp +++ b/src/waveform/renderers/waveformrenderbeat.cpp @@ -67,12 +67,13 @@ void WaveformRenderBeat::draw(QPainter* painter, QPaintEvent* /*event*/) { return; } - PainterScope PainterScope(painter); + //PainterScope PainterScope(painter); - painter->setRenderHint(QPainter::Antialiasing); + //painter->setRenderHint(QPainter::Antialiasing); QPen beatPen(m_beatColor); - beatPen.setWidthF(std::max(1.0, scaleFactor())); + //beatPen.setWidthF(std::max(1.0, scaleFactor())); + beatPen.setWidthF(1.0); painter->setPen(beatPen); const Qt::Orientation orientation = m_waveformRenderer->getOrientation(); diff --git a/src/waveform/vsyncthread.cpp b/src/waveform/vsyncthread.cpp index 4ec2e571c8df..5b196eb80de1 100644 --- a/src/waveform/vsyncthread.cpp +++ b/src/waveform/vsyncthread.cpp @@ -1,6 +1,5 @@ #include "vsyncthread.h" -#include #include #include #include diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp index 52ddb22f2a93..7930c31aa0fe 100644 --- a/src/waveform/waveformwidgetfactory.cpp +++ b/src/waveform/waveformwidgetfactory.cpp @@ -1,7 +1,10 @@ #include "waveform/waveformwidgetfactory.h" +#ifndef MIXXX_USE_QOPENGL #include #include +#endif + #include #include #include @@ -114,6 +117,12 @@ WaveformWidgetFactory::WaveformWidgetFactory() m_visualGain[Mid] = 1.0; m_visualGain[High] = 1.0; +#ifdef MIXXX_USE_QOPENGL + // TODO @m0dB We might want to check, but as this is intended for macOS + // we can be sure the OpenGL is available + m_openGlAvailable = true; + m_openGLShaderAvailable = true; +#else QGLWidget* pGlWidget = SharedGLContext::getWidget(); if (pGlWidget && pGlWidget->isValid()) { // will be false if SafeMode is enabled @@ -254,7 +263,7 @@ WaveformWidgetFactory::WaveformWidgetFactory() pGlWidget->hide(); } - +#endif evaluateWidgets(); m_time.start(); } @@ -674,7 +683,7 @@ void WaveformWidgetFactory::render() { } } - // WSpinnys are also double-buffered QGLWidgets, like all the waveform + // WSpinnys are also double-buffered WGLWidgets, like all the waveform // renderers. Render all the WSpinny widgets now. emit renderSpinnies(m_vsyncThread); // Same for WVuMeterGL. Note that we are either using WVuMeter or WVuMeterGL. diff --git a/src/waveform/widgets/glrgbwaveformwidget.cpp b/src/waveform/widgets/glrgbwaveformwidget.cpp index 99f607fe0574..260ac4118c3d 100644 --- a/src/waveform/widgets/glrgbwaveformwidget.cpp +++ b/src/waveform/widgets/glrgbwaveformwidget.cpp @@ -50,7 +50,7 @@ mixxx::Duration GLRGBWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/glsimplewaveformwidget.cpp b/src/waveform/widgets/glsimplewaveformwidget.cpp index 64a57e3eabe5..5b9166a94cc9 100644 --- a/src/waveform/widgets/glsimplewaveformwidget.cpp +++ b/src/waveform/widgets/glsimplewaveformwidget.cpp @@ -52,7 +52,7 @@ mixxx::Duration GLSimpleWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/glslwaveformwidget.cpp b/src/waveform/widgets/glslwaveformwidget.cpp index 6a7ac8bc831d..16c74884f538 100644 --- a/src/waveform/widgets/glslwaveformwidget.cpp +++ b/src/waveform/widgets/glslwaveformwidget.cpp @@ -80,7 +80,7 @@ mixxx::Duration GLSLWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/glvsynctestwidget.cpp b/src/waveform/widgets/glvsynctestwidget.cpp index 938bba5967d4..2df809bb6dec 100644 --- a/src/waveform/widgets/glvsynctestwidget.cpp +++ b/src/waveform/widgets/glvsynctestwidget.cpp @@ -54,7 +54,7 @@ mixxx::Duration GLVSyncTestWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/glwaveformwidget.cpp b/src/waveform/widgets/glwaveformwidget.cpp index 6279dc9decec..b6f829740846 100644 --- a/src/waveform/widgets/glwaveformwidget.cpp +++ b/src/waveform/widgets/glwaveformwidget.cpp @@ -54,7 +54,7 @@ mixxx::Duration GLWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/glwaveformwidgetabstract.h b/src/waveform/widgets/glwaveformwidgetabstract.h index 2b1b0e1582ae..46ff392d6cd1 100644 --- a/src/waveform/widgets/glwaveformwidgetabstract.h +++ b/src/waveform/widgets/glwaveformwidgetabstract.h @@ -5,13 +5,14 @@ #include "waveform/renderers/glwaveformrenderer.h" #include "waveform/widgets/waveformwidgetabstract.h" #include "widget/wglwidget.h" +#include "widget/wwaveformviewer.h" QT_FORWARD_DECLARE_CLASS(QString) /// GLWaveformWidgetAbstract is a WaveformWidgetAbstract & WGLWidget. Its optional /// member GLWaveformRenderer* m_pGlRenderer can implement a virtual method /// onInitializeGL, which will be called from GLWaveformRenderer::initializeGL -/// (which overrides QGLWidget::initializeGL). This can be used for initialization +/// (which overrides WGLWidget::initializeGL). This can be used for initialization /// that must be deferred until the GL context has been initialized and that can't /// be done in the constructor. class GLWaveformWidgetAbstract : public WaveformWidgetAbstract, public WGLWidget { @@ -38,6 +39,17 @@ class GLWaveformWidgetAbstract : public WaveformWidgetAbstract, public WGLWidget } } +#ifdef MIXXX_USE_QOPENGL + // We need to forward events coming from the QOpenGLWindow + // (drag&drop, mouse) to the viewer + void handleEventFromWindow(QEvent* ev) override { + auto viewer = dynamic_cast(parent()); + if (viewer) { + viewer->handleEventFromWindow(ev); + } + } +#endif + GLWaveformRenderer* m_pGlRenderer; #endif // !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2) }; diff --git a/src/waveform/widgets/qthsvwaveformwidget.cpp b/src/waveform/widgets/qthsvwaveformwidget.cpp index c1cc090fae2f..d886ef63bd4f 100644 --- a/src/waveform/widgets/qthsvwaveformwidget.cpp +++ b/src/waveform/widgets/qthsvwaveformwidget.cpp @@ -46,7 +46,7 @@ mixxx::Duration QtHSVWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/qtrgbwaveformwidget.cpp b/src/waveform/widgets/qtrgbwaveformwidget.cpp index aa3fc3d0efc8..1f5b16dd8369 100644 --- a/src/waveform/widgets/qtrgbwaveformwidget.cpp +++ b/src/waveform/widgets/qtrgbwaveformwidget.cpp @@ -49,7 +49,7 @@ mixxx::Duration QtRGBWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/qtsimplewaveformwidget.cpp b/src/waveform/widgets/qtsimplewaveformwidget.cpp index fcaf7bfab982..2469dc5155e3 100644 --- a/src/waveform/widgets/qtsimplewaveformwidget.cpp +++ b/src/waveform/widgets/qtsimplewaveformwidget.cpp @@ -52,7 +52,7 @@ mixxx::Duration QtSimpleWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/qtvsynctestwidget.cpp b/src/waveform/widgets/qtvsynctestwidget.cpp index 3e20ecbfb449..95519ec51442 100644 --- a/src/waveform/widgets/qtvsynctestwidget.cpp +++ b/src/waveform/widgets/qtvsynctestwidget.cpp @@ -44,7 +44,7 @@ mixxx::Duration QtVSyncTestWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/waveform/widgets/qtwaveformwidget.cpp b/src/waveform/widgets/qtwaveformwidget.cpp index 9b2c75dc46e9..fee4fb19aa5f 100644 --- a/src/waveform/widgets/qtwaveformwidget.cpp +++ b/src/waveform/widgets/qtwaveformwidget.cpp @@ -50,7 +50,7 @@ mixxx::Duration QtWaveformWidget::render() { timer.start(); // QPainter makes QGLContext::currentContext() == context() // this may delayed until previous buffer swap finished - QPainter painter(this); + QPainter painter(paintDevice()); t1 = timer.restart(); draw(&painter, nullptr); //t2 = timer.restart(); diff --git a/src/widget/openglwindow.cpp b/src/widget/openglwindow.cpp new file mode 100644 index 000000000000..c13f1d12380b --- /dev/null +++ b/src/widget/openglwindow.cpp @@ -0,0 +1,45 @@ +#include "widget/openglwindow.h" + +#include + +#include "widget/wglwidget.h" + +OpenGLWindow::OpenGLWindow(WGLWidget* widget) + : m_pWidget(widget) { +} + +OpenGLWindow::~OpenGLWindow() { +} + +void OpenGLWindow::initializeGL() { + if (m_pWidget) { + m_pWidget->initializeGL(); + } +} + +void OpenGLWindow::paintGL() { +} + +void OpenGLWindow::resizeGL(int w, int h) { +} + +void OpenGLWindow::widgetDestroyed() { + m_pWidget = nullptr; +} + +bool OpenGLWindow::event(QEvent* ev) { + bool result = QOpenGLWindow::event(ev); + + if (m_pWidget) { + const auto t = ev->type(); + // Forward the following events to the WGLWidget + if (t == QEvent::MouseButtonDblClick || t == QEvent::MouseButtonPress || + t == QEvent::MouseButtonRelease || t == QEvent::MouseMove || + t == QEvent::DragEnter || t == QEvent::DragLeave || + t == QEvent::DragMove || t == QEvent::Drop) { + m_pWidget->handleEventFromWindow(ev); + } + } + + return result; +} diff --git a/src/widget/openglwindow.h b/src/widget/openglwindow.h new file mode 100644 index 000000000000..6e0d5e40a4d6 --- /dev/null +++ b/src/widget/openglwindow.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class WGLWidget; + +/// Helper class used by wglwidgetqopengl + +class OpenGLWindow : public QOpenGLWindow { + Q_OBJECT + + WGLWidget* m_pWidget; + + public: + OpenGLWindow(WGLWidget* widget); + ~OpenGLWindow(); + + void widgetDestroyed(); + + private: + void initializeGL() override; + void paintGL() override; + void resizeGL(int w, int h) override; + bool event(QEvent* ev) override; +}; diff --git a/src/widget/paintable.cpp b/src/widget/paintable.cpp index 18af669b1621..221d709b84ea 100644 --- a/src/widget/paintable.cpp +++ b/src/widget/paintable.cpp @@ -134,6 +134,10 @@ QRectF Paintable::rect() const { return QRectF(); } +QImage Paintable::toImage() const { + return m_pPixmap.isNull() ? QImage() : m_pPixmap->toImage(); +} + void Paintable::draw(const QRectF& targetRect, QPainter* pPainter) { // The sourceRect is implicitly the entire Paintable. draw(targetRect, pPainter, rect()); diff --git a/src/widget/paintable.h b/src/widget/paintable.h index 01126a2e07ea..3dcb185cdf77 100644 --- a/src/widget/paintable.h +++ b/src/widget/paintable.h @@ -34,6 +34,7 @@ class Paintable { int width() const; int height() const; QRectF rect() const; + QImage toImage() const; DrawMode drawMode() const { return m_drawMode; } diff --git a/src/widget/wglwidget.h b/src/widget/wglwidget.h index 9a2f5fed166f..ecc042a47565 100644 --- a/src/widget/wglwidget.h +++ b/src/widget/wglwidget.h @@ -1,23 +1,13 @@ #pragma once -#include +// checked in wglwidgetqglwidget.h and wglwidgetqglwidget.h +// to make sure they are only included from this header +#define WGLWIDGET_H -class WGLWidget : public QGLWidget { - public: - WGLWidget(QWidget* parent); +#ifdef MIXXX_USE_QOPENGL +#include "widget/wglwidgetqopengl.h" +#else +#include "widget/wglwidgetqglwidget.h" +#endif - bool isContextValid() const; - bool isContextSharing() const; - - bool shouldRender() const; - - void makeCurrentIfNeeded(); -}; - -// Other QGLWidget / QWidget methods called: -// - void swapBuffers() { -// - To avoid xcb crash, in legacyskinparser.cpp -// - void setParent(QWidget* parent) { -// - Used by WSpinny: -// - void installEventFilter(QObject *); -// - connect +#undef WGLWIDGET_H diff --git a/src/widget/wglwidgetqglwidget.h b/src/widget/wglwidgetqglwidget.h new file mode 100644 index 000000000000..6dc56872434a --- /dev/null +++ b/src/widget/wglwidgetqglwidget.h @@ -0,0 +1,27 @@ +#pragma once + +#ifndef WGLWIDGET_H +#error "Do not include this file, include wglwidget.h instead" +#endif + +#include + +/////////////////////////////////////// +// WGLWidget as a subclass of QGLWidget +/////////////////////////////////////// + +class WGLWidget : public QGLWidget { + public: + WGLWidget(QWidget* parent); + + bool isContextValid() const; + bool isContextSharing() const; + + bool shouldRender() const; + + void makeCurrentIfNeeded(); + + QPaintDevice* paintDevice() { + return this; + } +}; diff --git a/src/widget/wglwidgetqopengl.cpp b/src/widget/wglwidgetqopengl.cpp new file mode 100644 index 000000000000..4e86743dc6b5 --- /dev/null +++ b/src/widget/wglwidgetqopengl.cpp @@ -0,0 +1,61 @@ +#include + +#include "widget/openglwindow.h" +#include "widget/wglwidget.h" + +WGLWidget::WGLWidget(QWidget* parent) + : QWidget(parent), m_pOpenGLWindow(new OpenGLWindow(this)) { + QSurfaceFormat format; + // TODO @m0dB should we set any particular format flags? + m_pOpenGLWindow->setFormat(format); + m_pContainerWidget = createWindowContainer(m_pOpenGLWindow, this); +} + +WGLWidget::~WGLWidget() { + m_pOpenGLWindow->widgetDestroyed(); +} + +QPaintDevice* WGLWidget::paintDevice() { + makeCurrentIfNeeded(); + return m_pOpenGLWindow; +} + +void WGLWidget::resizeEvent(QResizeEvent* event) { + const auto size = event->size(); + m_pContainerWidget->resize(size); +} + +void WGLWidget::handleEventFromWindow(QEvent* e) { + event(e); +} + +bool WGLWidget::isContextValid() const { + return m_pOpenGLWindow->context() && m_pOpenGLWindow->context()->isValid(); +} + +bool WGLWidget::isContextSharing() const { + return true; +} + +void WGLWidget::makeCurrentIfNeeded() { + if (m_pOpenGLWindow->context() != QOpenGLContext::currentContext()) { + m_pOpenGLWindow->makeCurrent(); + } +} + +void WGLWidget::initializeGL() { + // to be implemented in derived widgets if needed +} + +void WGLWidget::swapBuffers() { + makeCurrentIfNeeded(); + if (m_pOpenGLWindow->context()) { + m_pOpenGLWindow->context()->swapBuffers(m_pOpenGLWindow->context()->surface()); + } +} + +bool WGLWidget::shouldRender() const { + // TODO @m0dB this seems sufficient, but maybe there are more things + // to check here? + return m_pOpenGLWindow->isVisible(); +} diff --git a/src/widget/wglwidgetqopengl.h b/src/widget/wglwidgetqopengl.h new file mode 100644 index 000000000000..f709df61ab1d --- /dev/null +++ b/src/widget/wglwidgetqopengl.h @@ -0,0 +1,41 @@ +#pragma once + +#ifndef WGLWIDGET_H +#error "Do not include this file, include wglwidget.h instead" +#endif + +#include + +//////////////////////////////// +// QOpenGLWindow based WGLWidget +//////////////////////////////// + +class QPaintDevice; +class OpenGLWindow; + +class WGLWidget : public QWidget { + private: + OpenGLWindow* m_pOpenGLWindow{}; + QWidget* m_pContainerWidget{}; + + public: + WGLWidget(QWidget* parent); + ~WGLWidget(); + + bool isContextValid() const; + bool isContextSharing() const; + + bool shouldRender() const; + + void makeCurrentIfNeeded(); + + void swapBuffers(); + void resizeEvent(QResizeEvent* event); + + // called by OpenGLWindow + virtual void handleEventFromWindow(QEvent* ev); + virtual void initializeGL(); + + protected: + QPaintDevice* paintDevice(); +}; diff --git a/src/widget/wglwidgetqopenglwidget.cpp b/src/widget/wglwidgetqopenglwidget.cpp new file mode 100644 index 000000000000..aacdb29932c7 --- /dev/null +++ b/src/widget/wglwidgetqopenglwidget.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include "widget/wglwidget.h" + +WGLWidget::WGLWidget(QWidget* parent) + : QOpenGLWidget(parent) { + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_OpaquePaintEvent); + setAutoFillBackground(false); + //setAutoBufferSwap(false); + // Not interested in repaint or update calls, as we draw from the vsync thread + setUpdatesEnabled(false); +} + +bool WGLWidget::isContextValid() const { + return context() && context()->isValid(); +} + +bool WGLWidget::isContextSharing() const { + return false; //context()->isSharing(); +} + +bool WGLWidget::shouldRender() const { + return context(); + return isValid() && isVisible() && windowHandle() && windowHandle()->isExposed(); +} + +void WGLWidget::swapBuffers() { + if (context()) + context()->swapBuffers(context()->surface()); +} + +void WGLWidget::makeCurrentIfNeeded() { + // TODO m0dB is this really needed? is calling makeCurrent without this 'if' really a problem? + //if (context() != QGLContext::currentContext()) { + makeCurrent(); + //} +} diff --git a/src/widget/wknob.cpp b/src/widget/wknob.cpp index f31d1d15ba78..8f24b4ab4321 100644 --- a/src/widget/wknob.cpp +++ b/src/widget/wknob.cpp @@ -8,13 +8,18 @@ #include "util/duration.h" WKnob::WKnob(QWidget* pParent) - : WDisplay(pParent), + : WDisplay(pParent) +#ifdef USE_WIDGET_RENDER_TIMER + , m_renderTimer(mixxx::Duration::fromMillis(20), - mixxx::Duration::fromSeconds(1)) { + mixxx::Duration::fromSeconds(1)) { connect(&m_renderTimer, &WidgetRenderTimer::update, this, QOverload<>::of(&QWidget::update)); +#else +{ +#endif setFocusPolicy(Qt::NoFocus); } @@ -39,7 +44,7 @@ void WKnob::wheelEvent(QWheelEvent* e) { } void WKnob::inputActivity() { -#ifdef __APPLE__ +#ifdef USE_WIDGET_RENDER_TIMER m_renderTimer.activity(); #else update(); diff --git a/src/widget/wknob.h b/src/widget/wknob.h index 95ffe710794b..be049ef56766 100644 --- a/src/widget/wknob.h +++ b/src/widget/wknob.h @@ -28,8 +28,9 @@ class WKnob : public WDisplay { void inputActivity(); private: +#ifdef USE_WIDGET_RENDER_TIMER WidgetRenderTimer m_renderTimer; - +#endif KnobEventHandler m_handler; friend class KnobEventHandler; }; diff --git a/src/widget/wknobcomposed.cpp b/src/widget/wknobcomposed.cpp index cc318ac80dde..a6736144b4ba 100644 --- a/src/widget/wknobcomposed.cpp +++ b/src/widget/wknobcomposed.cpp @@ -20,13 +20,18 @@ WKnobComposed::WKnobComposed(QWidget* pParent) m_dArcBgThickness(0), m_arcUnipolar(true), m_arcReversed(false), - m_arcPenCap(Qt::FlatCap), + m_arcPenCap(Qt::FlatCap) +#ifdef USE_WIDGET_RENDER_TIMER + , m_renderTimer(mixxx::Duration::fromMillis(20), - mixxx::Duration::fromSeconds(1)) { + mixxx::Duration::fromSeconds(1)) { connect(&m_renderTimer, &WidgetRenderTimer::update, this, QOverload<>::of(&QWidget::update)); +#else +{ +#endif } void WKnobComposed::setup(const QDomNode& node, const SkinContext& context) { @@ -228,7 +233,7 @@ void WKnobComposed::wheelEvent(QWheelEvent* e) { } void WKnobComposed::inputActivity() { -#ifdef __APPLE__ +#ifdef USE_WIDGET_RENDER_TIMER m_renderTimer.activity(); #else update(); diff --git a/src/widget/wknobcomposed.h b/src/widget/wknobcomposed.h index cf3e287575c2..861c024552db 100644 --- a/src/widget/wknobcomposed.h +++ b/src/widget/wknobcomposed.h @@ -63,7 +63,9 @@ class WKnobComposed : public WWidget { bool m_arcUnipolar; bool m_arcReversed; Qt::PenCapStyle m_arcPenCap; +#ifdef USE_WIDGET_RENDER_TIMER WidgetRenderTimer m_renderTimer; +#endif friend class KnobEventHandler; }; diff --git a/src/widget/wslidercomposed.cpp b/src/widget/wslidercomposed.cpp index 8f0c1574b299..eb18a4477b64 100644 --- a/src/widget/wslidercomposed.cpp +++ b/src/widget/wslidercomposed.cpp @@ -12,30 +12,35 @@ #include "widget/wpixmapstore.h" #include "widget/wskincolor.h" -WSliderComposed::WSliderComposed(QWidget * parent) - : WWidget(parent), - m_dHandleLength(0.0), - m_dSliderLength(0.0), - m_bHorizontal(false), - m_dBarWidth(0.0), - m_dBarBgWidth(0.0), - m_dBarStart(0.0), - m_dBarEnd(0.0), - m_dBarBgStart(0.0), - m_dBarBgEnd(0.0), - m_dBarAxisPos(0.0), - m_bBarUnipolar(true), - m_barColor(nullptr), - m_barBgColor(nullptr), - m_barPenCap(Qt::FlatCap), - m_pSlider(nullptr), - m_pHandle(nullptr), - m_renderTimer(mixxx::Duration::fromMillis(20), - mixxx::Duration::fromSeconds(1)) { +WSliderComposed::WSliderComposed(QWidget* parent) + : WWidget(parent), + m_dHandleLength(0.0), + m_dSliderLength(0.0), + m_bHorizontal(false), + m_dBarWidth(0.0), + m_dBarBgWidth(0.0), + m_dBarStart(0.0), + m_dBarEnd(0.0), + m_dBarBgStart(0.0), + m_dBarBgEnd(0.0), + m_dBarAxisPos(0.0), + m_bBarUnipolar(true), + m_barColor(nullptr), + m_barBgColor(nullptr), + m_barPenCap(Qt::FlatCap), + m_pSlider(nullptr), + m_pHandle(nullptr) +#ifdef USE_WIDGET_RENDER_TIMER + , + m_renderTimer(mixxx::Duration::fromMillis(20), + mixxx::Duration::fromSeconds(1)) { connect(&m_renderTimer, &WidgetRenderTimer::update, this, QOverload<>::of(&QWidget::update)); +#else + { +#endif } WSliderComposed::~WSliderComposed() { @@ -371,7 +376,7 @@ double WSliderComposed::calculateHandleLength() { } void WSliderComposed::inputActivity() { -#ifdef __APPLE__ +#ifdef USE_WIDGET_RENDER_TIMER m_renderTimer.activity(); #else update(); diff --git a/src/widget/wslidercomposed.h b/src/widget/wslidercomposed.h index 9162bba9946f..1360eb16f107 100644 --- a/src/widget/wslidercomposed.h +++ b/src/widget/wslidercomposed.h @@ -75,7 +75,9 @@ class WSliderComposed : public WWidget { // Pointer to pixmap of the handle PaintablePointer m_pHandle; SliderEventHandler m_handler; +#ifdef USE_WIDGET_RENDER_TIMER WidgetRenderTimer m_renderTimer; +#endif friend class SliderEventHandler; }; diff --git a/src/widget/wspinny.cpp b/src/widget/wspinny.cpp index b58b1a109e44..aaaa3fd0d1f7 100644 --- a/src/widget/wspinny.cpp +++ b/src/widget/wspinny.cpp @@ -333,7 +333,7 @@ void WSpinny::render(VSyncThread* vSyncThread) { double scaleFactor = devicePixelRatioF(); - QPainter p(this); + QPainter p(paintDevice()); p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::SmoothPixmapTransform); @@ -421,7 +421,7 @@ QPixmap WSpinny::scaledCoverArt(const QPixmap& normal) { return scaled; } -void WSpinny::resizeEvent(QResizeEvent* /*unused*/) { +void WSpinny::resizeEvent(QResizeEvent* event) { m_loadedCoverScaled = scaledCoverArt(m_loadedCover); if (m_pFgImage && !m_pFgImage->isNull()) { m_fgImageScaled = m_pFgImage->scaled( @@ -431,6 +431,7 @@ void WSpinny::resizeEvent(QResizeEvent* /*unused*/) { m_ghostImageScaled = m_pGhostImage->scaled( size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } + WGLWidget::resizeEvent(event); } /* Convert between a normalized playback position (0.0 - 1.0) and an angle diff --git a/src/widget/wvumetergl.h b/src/widget/wvumetergl.h index f5b1d8347ac7..a4d2eb9e4e13 100644 --- a/src/widget/wvumetergl.h +++ b/src/widget/wvumetergl.h @@ -1,73 +1,7 @@ #pragma once -#include "skin/legacy/skincontext.h" -#include "util/duration.h" -#include "widget/wglwidget.h" -#include "widget/wpixmapstore.h" -#include "widget/wwidget.h" - -class VSyncThread; - -class WVuMeterGL : public WGLWidget, public WBaseWidget { - Q_OBJECT - public: - explicit WVuMeterGL(QWidget* parent = nullptr); - - void setup(const QDomNode& node, const SkinContext& context); - void setPixmapBackground( - const PixmapSource& source, - Paintable::DrawMode mode, - double scaleFactor); - void setPixmaps( - const PixmapSource& source, - bool bHorizontal, - Paintable::DrawMode mode, - double scaleFactor); - void onConnectedControlChanged(double dParameter, double dValue) override; - - public slots: - void render(VSyncThread* vSyncThread); - void swap(); - - protected slots: - void updateState(mixxx::Duration elapsed); - - private: - void paintEvent(QPaintEvent* /*unused*/) override; - void showEvent(QShowEvent* /*unused*/) override; - void setPeak(double parameter); - - // To make sure we render at least once even when we have no signal - bool m_bHasRendered; - // To indicate that we rendered so we need to swap - bool m_bSwapNeeded; - // Current parameter and peak parameter. - double m_dParameter; - double m_dPeakParameter; - - // The last parameter and peak parameter values at the time of - // rendering. Used to check whether the widget state has changed since the - // last render in maybeUpdate. - double m_dLastParameter; - double m_dLastPeakParameter; - - // Length of the VU-meter pixmap along the relevant axis. - int m_iPixmapLength; - - // Associated pixmaps - PaintablePointer m_pPixmapBack; - PaintablePointer m_pPixmapVu; - - // True if it's a horizontal vu meter - bool m_bHorizontal; - - int m_iPeakHoldSize; - int m_iPeakFallStep; - int m_iPeakHoldTime; - int m_iPeakFallTime; - - // The peak hold time remaining in milliseconds. - double m_dPeakHoldCountdownMs; - - QColor m_qBgColor; -}; +#ifdef MIXXX_USE_QOPENGL +#include "widget/wvumeterglqopengl.h" +#else +#include "widget/wvumeterglqgl.h" +#endif diff --git a/src/widget/wvumetergl.cpp b/src/widget/wvumeterglqgl.cpp similarity index 99% rename from src/widget/wvumetergl.cpp rename to src/widget/wvumeterglqgl.cpp index 9c17d57eebce..ed9e492f70c1 100644 --- a/src/widget/wvumetergl.cpp +++ b/src/widget/wvumeterglqgl.cpp @@ -174,7 +174,7 @@ void WVuMeterGL::render(VSyncThread* vSyncThread) { return; } - QPainter p(this); + QPainter p(paintDevice()); // fill the background, in case the image contains transparency p.fillRect(rect(), m_qBgColor); diff --git a/src/widget/wvumeterglqgl.h b/src/widget/wvumeterglqgl.h new file mode 100644 index 000000000000..f5b1d8347ac7 --- /dev/null +++ b/src/widget/wvumeterglqgl.h @@ -0,0 +1,73 @@ +#pragma once + +#include "skin/legacy/skincontext.h" +#include "util/duration.h" +#include "widget/wglwidget.h" +#include "widget/wpixmapstore.h" +#include "widget/wwidget.h" + +class VSyncThread; + +class WVuMeterGL : public WGLWidget, public WBaseWidget { + Q_OBJECT + public: + explicit WVuMeterGL(QWidget* parent = nullptr); + + void setup(const QDomNode& node, const SkinContext& context); + void setPixmapBackground( + const PixmapSource& source, + Paintable::DrawMode mode, + double scaleFactor); + void setPixmaps( + const PixmapSource& source, + bool bHorizontal, + Paintable::DrawMode mode, + double scaleFactor); + void onConnectedControlChanged(double dParameter, double dValue) override; + + public slots: + void render(VSyncThread* vSyncThread); + void swap(); + + protected slots: + void updateState(mixxx::Duration elapsed); + + private: + void paintEvent(QPaintEvent* /*unused*/) override; + void showEvent(QShowEvent* /*unused*/) override; + void setPeak(double parameter); + + // To make sure we render at least once even when we have no signal + bool m_bHasRendered; + // To indicate that we rendered so we need to swap + bool m_bSwapNeeded; + // Current parameter and peak parameter. + double m_dParameter; + double m_dPeakParameter; + + // The last parameter and peak parameter values at the time of + // rendering. Used to check whether the widget state has changed since the + // last render in maybeUpdate. + double m_dLastParameter; + double m_dLastPeakParameter; + + // Length of the VU-meter pixmap along the relevant axis. + int m_iPixmapLength; + + // Associated pixmaps + PaintablePointer m_pPixmapBack; + PaintablePointer m_pPixmapVu; + + // True if it's a horizontal vu meter + bool m_bHorizontal; + + int m_iPeakHoldSize; + int m_iPeakFallStep; + int m_iPeakHoldTime; + int m_iPeakFallTime; + + // The peak hold time remaining in milliseconds. + double m_dPeakHoldCountdownMs; + + QColor m_qBgColor; +}; diff --git a/src/widget/wvumeterglqopengl.cpp b/src/widget/wvumeterglqopengl.cpp new file mode 100644 index 000000000000..8bc1ce1b3fa6 --- /dev/null +++ b/src/widget/wvumeterglqopengl.cpp @@ -0,0 +1,511 @@ +#include "moc_wvumetergl.cpp" +#include "util/math.h" +#include "util/timer.h" +#include "util/widgethelper.h" +#include "waveform/sharedglcontext.h" +#include "waveform/vsyncthread.h" +#include "widget/wpixmapstore.h" +#include "widget/wvumetergl.h" + +#define DEFAULT_FALLTIME 20 +#define DEFAULT_FALLSTEP 1 +#define DEFAULT_HOLDTIME 400 +#define DEFAULT_HOLDSIZE 5 + +WVuMeterGL::WVuMeterGL(QWidget* parent) + : WGLWidget(parent), + WBaseWidget(this), + m_bHasRendered(false), + m_bSwapNeeded(false), + m_dParameter(0), + m_dPeakParameter(0), + m_dLastParameter(0), + m_dLastPeakParameter(0), + m_iPixmapLength(0), + m_bHorizontal(false), + m_iPeakHoldSize(0), + m_iPeakFallStep(0), + m_iPeakHoldTime(0), + m_iPeakFallTime(0), + m_dPeakHoldCountdownMs(0) { + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_OpaquePaintEvent); + + setAutoFillBackground(false); + //setAutoBufferSwap(false); + + // Not interested in repaint or update calls, as we draw from the vsync thread + setUpdatesEnabled(false); +} + +void WVuMeterGL::setup(const QDomNode& node, const SkinContext& context) { + // Set pixmaps + bool bHorizontal = false; + (void)context.hasNodeSelectBool(node, "Horizontal", &bHorizontal); + + // Set background pixmap if available + QDomElement backPathNode = context.selectElement(node, "PathBack"); + if (!backPathNode.isNull()) { + // The implicit default in <1.12.0 was FIXED so we keep it for backwards + // compatibility. + setPixmapBackground( + context.getPixmapSource(backPathNode), + context.selectScaleMode(backPathNode, Paintable::FIXED), + context.getScaleFactor()); + } + + QDomElement vuNode = context.selectElement(node, "PathVu"); + // The implicit default in <1.12.0 was FIXED so we keep it for backwards + // compatibility. + setPixmaps(context.getPixmapSource(vuNode), + bHorizontal, + context.selectScaleMode(vuNode, Paintable::FIXED), + context.getScaleFactor()); + + m_iPeakHoldSize = context.selectInt(node, "PeakHoldSize"); + if (m_iPeakHoldSize < 0 || m_iPeakHoldSize > 100) { + m_iPeakHoldSize = DEFAULT_HOLDSIZE; + } + + m_iPeakFallStep = context.selectInt(node, "PeakFallStep"); + if (m_iPeakFallStep < 1 || m_iPeakFallStep > 1000) { + m_iPeakFallStep = DEFAULT_FALLSTEP; + } + + m_iPeakHoldTime = context.selectInt(node, "PeakHoldTime"); + if (m_iPeakHoldTime < 1 || m_iPeakHoldTime > 3000) { + m_iPeakHoldTime = DEFAULT_HOLDTIME; + } + + m_iPeakFallTime = context.selectInt(node, "PeakFallTime"); + if (m_iPeakFallTime < 1 || m_iPeakFallTime > 1000) { + m_iPeakFallTime = DEFAULT_FALLTIME; + } + + setFocusPolicy(Qt::NoFocus); +} + +void WVuMeterGL::setPixmapBackground( + const PixmapSource& source, + Paintable::DrawMode mode, + double scaleFactor) { + m_pPixmapBack = WPixmapStore::getPaintable(source, mode, scaleFactor); + if (m_pPixmapBack.isNull()) { + qDebug() << metaObject()->className() + << "Error loading background pixmap:" << source.getPath(); + } else if (mode == Paintable::FIXED) { + setFixedSize(m_pPixmapBack->size()); + } +} + +void WVuMeterGL::setPixmaps(const PixmapSource& source, + bool bHorizontal, + Paintable::DrawMode mode, + double scaleFactor) { + m_pPixmapVu = WPixmapStore::getPaintable(source, mode, scaleFactor); + if (m_pPixmapVu.isNull()) { + qDebug() << "WVuMeterGL: Error loading vu pixmap" << source.getPath(); + } else { + m_bHorizontal = bHorizontal; + if (m_bHorizontal) { + m_iPixmapLength = m_pPixmapVu->width(); + } else { + m_iPixmapLength = m_pPixmapVu->height(); + } + } +} + +void WVuMeterGL::onConnectedControlChanged(double dParameter, double dValue) { + Q_UNUSED(dValue); + m_dParameter = math_clamp(dParameter, 0.0, 1.0); + + if (dParameter > 0.0) { + setPeak(dParameter); + } else { + // A 0.0 value is very unlikely except when the VU Meter is disabled + m_dPeakParameter = 0; + } +} + +void WVuMeterGL::setPeak(double parameter) { + if (parameter > m_dPeakParameter) { + m_dPeakParameter = parameter; + m_dPeakHoldCountdownMs = m_iPeakHoldTime; + } +} + +void WVuMeterGL::updateState(mixxx::Duration elapsed) { + double msecsElapsed = elapsed.toDoubleMillis(); + // If we're holding at a peak then don't update anything + m_dPeakHoldCountdownMs -= msecsElapsed; + if (m_dPeakHoldCountdownMs > 0) { + return; + } else { + m_dPeakHoldCountdownMs = 0; + } + + // Otherwise, decrement the peak position by the fall step size times the + // milliseconds elapsed over the fall time multiplier. The peak will fall + // FallStep times (out of 128 steps) every FallTime milliseconds. + m_dPeakParameter -= static_cast(m_iPeakFallStep) * + msecsElapsed / + static_cast(m_iPeakFallTime * m_iPixmapLength); + m_dPeakParameter = math_clamp(m_dPeakParameter, 0.0, 1.0); +} + +void WVuMeterGL::paintEvent(QPaintEvent* e) { + Q_UNUSED(e); + // Force a rerender when render is called from the vsync thread, e.g. to + // git rid artifacts after hiding and showing the mixer or incomplete + // initial drawing. + m_bHasRendered = false; +} + +void WVuMeterGL::showEvent(QShowEvent* e) { + Q_UNUSED(e); + // Find the base color recursively in parent widget. + m_qBgColor = mixxx::widgethelper::findBaseColor(this); +} + +void WVuMeterGL::initializeGL() { + m_bHasRendered = false; + + m_initializeCalled = true; + + m_pTextureBack = new QOpenGLTexture(m_pPixmapBack->toImage()); + m_pTextureBack->setMinificationFilter(QOpenGLTexture::Linear); + m_pTextureBack->setMagnificationFilter(QOpenGLTexture::Linear); + m_pTextureBack->setWrapMode(QOpenGLTexture::ClampToBorder); + + m_pTextureVu = new QOpenGLTexture(m_pPixmapVu->toImage()); + m_pTextureVu->setMinificationFilter(QOpenGLTexture::Linear); + m_pTextureVu->setMagnificationFilter(QOpenGLTexture::Linear); + m_pTextureVu->setWrapMode(QOpenGLTexture::ClampToBorder); + + QString vertexShaderCode = + "\ +attribute vec4 position;\n\ +attribute vec3 texcoor;\n\ +varying vec3 vTexcoor;\n\ +void main()\n\ +{\n\ + vTexcoor = texcoor;\n\ + gl_Position = position;\n\ +}\n"; + + QString fragmentShaderCode = + "\ +uniform sampler2D m_sampler;\n\ +varying vec3 vTexcoor;\n\ +void main()\n\ +{\n\ + vec4 m_tex = texture2D(m_sampler, vec2(vTexcoor.x, vTexcoor.y));\n\ + gl_FragColor = m_tex;\n\ +}\n"; + + if (!m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderCode)) { + return; + } + + if (!m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderCode)) { + return; + } + + if (!m_shaderProgram.link()) { + return; + } + + if (!m_shaderProgram.bind()) { + return; + } +} + +void WVuMeterGL::render(VSyncThread* vSyncThread) { + ScopedTimer t("WVuMeterGL::render"); + + updateState(vSyncThread->sinceLastSwap()); + + if (m_bHasRendered && m_dParameter == m_dLastParameter && + m_dPeakParameter == m_dLastPeakParameter) { + return; + } + + if (!shouldRender()) { + return; + } + + makeCurrentIfNeeded(); + drawNativeGL(); +} + +void WVuMeterGL::draw(QPainter* painter) { + QPainter& p = *painter; + // fill the background, in case the image contains transparency + p.fillRect(rect(), m_qBgColor); + + if (!m_pPixmapBack.isNull()) { + // Draw background. + QRectF sourceRect(0, 0, m_pPixmapBack->width(), m_pPixmapBack->height()); + m_pPixmapBack->draw(rect(), &p, sourceRect); + } + + const double widgetWidth = width(); + const double widgetHeight = height(); + const double pixmapWidth = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->width(); + const double pixmapHeight = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->height(); + + // Draw (part of) vu + if (m_bHorizontal) { + { + const double widgetPosition = math_clamp(widgetWidth * m_dParameter, 0.0, widgetWidth); + QRectF targetRect(0, 0, widgetPosition, widgetHeight); + + if (!m_pPixmapVu.isNull()) { + const double pixmapPosition = math_clamp( + pixmapWidth * m_dParameter, 0.0, pixmapWidth); + QRectF sourceRect(0, 0, pixmapPosition, pixmapHeight); + m_pPixmapVu->draw(targetRect, &p, sourceRect); + } else { + // fallback to green rectangle + p.fillRect(targetRect, QColor(0, 255, 0)); + } + } + + if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 && + m_dPeakParameter > m_dParameter) { + const double widgetPeakPosition = math_clamp( + widgetWidth * m_dPeakParameter, 0.0, widgetWidth); + const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize); + const double widgetPeakHoldSize = widgetWidth * pixmapPeakHoldSize / pixmapHeight; + + QRectF targetRect(widgetPeakPosition - widgetPeakHoldSize, + 0, + widgetPeakHoldSize, + widgetHeight); + + if (!m_pPixmapVu.isNull()) { + const double pixmapPeakPosition = math_clamp( + pixmapWidth * m_dPeakParameter, 0.0, pixmapWidth); + + QRectF sourceRect = + QRectF(pixmapPeakPosition - pixmapPeakHoldSize, + 0, + pixmapPeakHoldSize, + pixmapHeight); + m_pPixmapVu->draw(targetRect, &p, sourceRect); + } else { + // fallback to green rectangle + p.fillRect(targetRect, QColor(0, 255, 0)); + } + } + } else { + // vertical + { + const double widgetPosition = + math_clamp(widgetHeight * m_dParameter, 0.0, widgetHeight); + QRectF targetRect(0, widgetHeight - widgetPosition, widgetWidth, widgetPosition); + + if (!m_pPixmapVu.isNull()) { + const double pixmapPosition = math_clamp( + pixmapHeight * m_dParameter, 0.0, pixmapHeight); + QRectF sourceRect(0, pixmapHeight - pixmapPosition, pixmapWidth, pixmapPosition); + m_pPixmapVu->draw(targetRect, &p, sourceRect); + } else { + // fallback to green rectangle + p.fillRect(targetRect, QColor(0, 255, 0)); + } + } + + if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 && + m_dPeakParameter > m_dParameter) { + const double widgetPeakPosition = math_clamp( + widgetHeight * m_dPeakParameter, 0.0, widgetHeight); + const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize); + const double widgetPeakHoldSize = widgetHeight * pixmapPeakHoldSize / pixmapHeight; + + QRectF targetRect(0, + widgetHeight - widgetPeakPosition, + widgetWidth, + widgetPeakHoldSize); + + if (!m_pPixmapVu.isNull()) { + const double pixmapPeakPosition = math_clamp( + pixmapHeight * m_dPeakParameter, 0.0, pixmapHeight); + + QRectF sourceRect = QRectF(0, + pixmapHeight - pixmapPeakPosition, + pixmapWidth, + pixmapPeakHoldSize); + m_pPixmapVu->draw(targetRect, &p, sourceRect); + } else { + // fallback to green rectangle + p.fillRect(targetRect, QColor(0, 255, 0)); + } + } + } + + m_dLastParameter = m_dParameter; + m_dLastPeakParameter = m_dPeakParameter; + m_bHasRendered = true; + m_bSwapNeeded = true; +} + +void WVuMeterGL::fillRectNativeGL(const QRectF& rect, const QColor& color) { + float x1 = -1.f + 2.f * rect.x() / width(); + float y1 = 1.f - 2.f * rect.y() / height(); + float x2 = x1 + 2.f * rect.width() / width(); + float y2 = y1 - 2.f * rect.height() / height(); + + glBegin(GL_TRIANGLE_STRIP); + glColor3f(color.redF(), color.greenF(), color.blueF()); + glVertex2f(x1, y1); + glVertex2f(x2, y1); + glVertex2f(x1, y2); + glVertex2f(x2, y2); + glEnd(); +} + +void WVuMeterGL::drawNativeGL() { + glClearColor(m_qBgColor.redF(), m_qBgColor.greenF(), m_qBgColor.blueF(), 1.f); + glClear(GL_COLOR_BUFFER_BIT); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + m_shaderProgram.bind(); + + if (m_pTextureBack) { + drawTexture(m_pTextureBack, 0, 0, 1, 1); + } + + const double widgetWidth = width(); + const double widgetHeight = height(); + const double pixmapWidth = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->width(); + const double pixmapHeight = m_pPixmapVu.isNull() ? 0 : m_pPixmapVu->height(); + + // Draw (part of) vu + if (m_bHorizontal) { + { + if (m_pTextureVu) { + drawTexture(m_pTextureVu, 0.f, 0.f, math_clamp(m_dParameter, 0.f, 1.f), 1.f); + } else { + // fallback to green rectangle + const double widgetPosition = math_clamp( + widgetWidth * m_dParameter, 0.0, widgetWidth); + QRectF targetRect(0, 0, widgetPosition, widgetHeight); + fillRectNativeGL(targetRect, QColor(0, 255, 0)); + } + } + + if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 && + m_dPeakParameter > m_dParameter) { + const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize); + const double peakholdSize = pixmapPeakHoldSize / pixmapWidth; + + if (m_pTextureVu) { + drawTexture(m_pTextureVu, + math_clamp( + m_dPeakParameter - peakholdSize, 0.f, 1.f), + 0.f, + peakholdSize, + 1.f); + } else { + const double widgetPeakPosition = math_clamp( + widgetWidth * m_dPeakParameter, 0.0, widgetWidth); + const double widgetPeakHoldSize = widgetWidth * pixmapPeakHoldSize / pixmapWidth; + QRectF targetRect(widgetPeakPosition - widgetPeakHoldSize, + 0, + widgetPeakHoldSize, + widgetHeight); + + // fallback to green rectangle + fillRectNativeGL(targetRect, QColor(0, 255, 0)); + } + } + } else { + // vertical + { + if (m_pTextureVu) { + drawTexture(m_pTextureVu, 0.f, 0.f, 1.f, math_clamp(m_dParameter, 0.f, 1.f)); + } else { + // fallback to green rectangle + const double widgetPosition = math_clamp( + widgetHeight * m_dParameter, 0.0, widgetHeight); + QRectF targetRect(0, widgetHeight - widgetPosition, widgetWidth, widgetPosition); + fillRectNativeGL(targetRect, QColor(0, 255, 0)); + } + } + + if (m_iPeakHoldSize > 0 && m_dPeakParameter > 0.0 && + m_dPeakParameter > m_dParameter) { + const double pixmapPeakHoldSize = static_cast(m_iPeakHoldSize); + const double peakholdSize = pixmapPeakHoldSize / pixmapHeight; + + if (m_pTextureVu) { + drawTexture(m_pTextureVu, + 0.f, + math_clamp( + m_dPeakParameter - peakholdSize, 0.f, 1.f), + 1.f, + peakholdSize); + } else { + const double widgetPeakPosition = math_clamp( + widgetHeight * m_dPeakParameter, 0.0, widgetHeight); + const double widgetPeakHoldSize = widgetHeight * pixmapPeakHoldSize / pixmapHeight; + + // fallback to green rectangle + QRectF targetRect(0, + widgetHeight - widgetPeakPosition, + widgetWidth, + widgetPeakHoldSize); + fillRectNativeGL(targetRect, QColor(0, 255, 0)); + } + } + } + + m_dLastParameter = m_dParameter; + m_dLastPeakParameter = m_dPeakParameter; + m_bHasRendered = true; + m_bSwapNeeded = true; +} + +void WVuMeterGL::swap() { + if (!shouldRender() || !m_bSwapNeeded) { + return; + } + makeCurrentIfNeeded(); + swapBuffers(); + m_bSwapNeeded = false; +} + +void WVuMeterGL::drawTexture(QOpenGLTexture* texture, float x, float y, float w, float h) { + const float texx1 = 1.f - x; + const float texy1 = 1.f - y; + const float texx2 = texx1 - w; + const float texy2 = texy1 - h; + + const float posx1 = -1.f + 2.f * x; + const float posx2 = posx1 + 2.f * w; + const float posy1 = -1.f + 2.f * y; + const float posy2 = posy1 + 2.f * h; + + const float posarray[] = {posx1, posy1, posx2, posy1, posx1, posy2, posx2, posy2}; + const float texarray[] = {texx1, texy1, texx2, texy1, texx1, texy2, texx2, texy2}; + + int vectlocation = m_shaderProgram.attributeLocation("position"); + int texcoordLocation = m_shaderProgram.attributeLocation("texcoor"); + + m_shaderProgram.enableAttributeArray(vectlocation); + m_shaderProgram.setAttributeArray( + vectlocation, GL_FLOAT, posarray, 2); + m_shaderProgram.enableAttributeArray(texcoordLocation); + m_shaderProgram.setAttributeArray( + texcoordLocation, GL_FLOAT, texarray, 2); + + m_shaderProgram.setUniformValue("sampler", 0); + + texture->bind(); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} diff --git a/src/widget/wvumeterglqopengl.h b/src/widget/wvumeterglqopengl.h new file mode 100644 index 000000000000..f9f14a0ae61f --- /dev/null +++ b/src/widget/wvumeterglqopengl.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include "skin/legacy/skincontext.h" +#include "util/duration.h" +#include "widget/wglwidget.h" +#include "widget/wpixmapstore.h" +#include "widget/wwidget.h" + +class VSyncThread; + +class WVuMeterGL : public WGLWidget, public WBaseWidget { + Q_OBJECT + public: + int m_id; + explicit WVuMeterGL(QWidget* parent = nullptr); + + void setup(const QDomNode& node, const SkinContext& context); + void setPixmapBackground( + const PixmapSource& source, + Paintable::DrawMode mode, + double scaleFactor); + void setPixmaps( + const PixmapSource& source, + bool bHorizontal, + Paintable::DrawMode mode, + double scaleFactor); + void onConnectedControlChanged(double dParameter, double dValue) override; + + void initializeGL() override; + + public slots: + void render(VSyncThread* vSyncThread); + void swap(); + + protected slots: + void updateState(mixxx::Duration elapsed); + + private: + void draw(QPainter* painter); + void drawNativeGL(); + void fillRectNativeGL(const QRectF& rect, const QColor& color); + void paintEvent(QPaintEvent* /*unused*/) override; + void showEvent(QShowEvent* /*unused*/) override; + void setPeak(double parameter); + + // To make sure we render at least once even when we have no signal + bool m_bHasRendered; + // To indicate that we rendered so we need to swap + bool m_bSwapNeeded; + // Current parameter and peak parameter. + double m_dParameter; + double m_dPeakParameter; + + // The last parameter and peak parameter values at the time of + // rendering. Used to check whether the widget state has changed since the + // last render in maybeUpdate. + double m_dLastParameter; + double m_dLastPeakParameter; + + // Length of the VU-meter pixmap along the relevant axis. + int m_iPixmapLength; + + // Associated pixmaps + PaintablePointer m_pPixmapBack; + PaintablePointer m_pPixmapVu; + + // True if it's a horizontal vu meter + bool m_bHorizontal; + + int m_iPeakHoldSize; + int m_iPeakFallStep; + int m_iPeakHoldTime; + int m_iPeakFallTime; + + // The peak hold time remaining in milliseconds. + double m_dPeakHoldCountdownMs; + + QColor m_qBgColor; + + bool m_initializeCalled{}; + bool m_paintCalled{}; + + void drawTexture(QOpenGLTexture* texture, float x, float y, float w, float h); + + QOpenGLTexture *m_pTextureBack, *m_pTextureVu; + QOpenGLShaderProgram m_shaderProgram; +}; diff --git a/src/widget/wwaveformviewer.h b/src/widget/wwaveformviewer.h index 4afbe391d7d6..309974877ea6 100644 --- a/src/widget/wwaveformviewer.h +++ b/src/widget/wwaveformviewer.h @@ -40,6 +40,13 @@ class WWaveformViewer : public WWidget, public TrackDropTarget { void mouseReleaseEvent(QMouseEvent * /*unused*/) override; void leaveEvent(QEvent* /*unused*/) override; +#ifdef MIXXX_USE_QOPENGL + // Handle the event forwarded from the QOpenGLWindow + void handleEventFromWindow(QEvent* e) { + event(e); + } +#endif + signals: void trackDropped(const QString& filename, const QString& group) override; void cloneDeck(const QString& sourceGroup, const QString& targetGroup) override;