File indexing completed on 2024-04-21 04:51:47

0001 /*
0002     SPDX-FileCopyrightText: 2011-2016 Meltytech LLC
0003     SPDX-FileCopyrightText: Dan Dennedy <dan@dennedy.org>
0004     SPDX-FileCopyrightText: Jean-Baptiste Mardelle
0005 
0006     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 
0008     GL shader based on BSD licensed code from Peter Bengtsson:
0009     https://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c
0010     SPDX-FileCopyrightText: 2004 Peter Bengtsson
0011 
0012     SPDX-License-Identifier: BSD-3-Clause
0013 */
0014 
0015 #include <QApplication>
0016 #include <QFontDatabase>
0017 #include <QOpenGLContext>
0018 #include <QOpenGLFunctions_3_2_Core>
0019 #include <QPainter>
0020 #include <QQmlContext>
0021 #include <QQuickItem>
0022 #include <QtGlobal>
0023 #include <memory>
0024 
0025 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0026 #include "kdeclarative_version.h"
0027 #endif
0028 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || KDECLARATIVE_VERSION > QT_VERSION_CHECK(5, 98, 0)
0029 #include <KQuickIconProvider>
0030 #else
0031 #include <KDeclarative/KDeclarative>
0032 #endif
0033 #include <KLocalizedContext>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 
0037 #include "bin/model/markersortmodel.h"
0038 #include "core.h"
0039 #include "glwidget.h"
0040 #include "monitorproxy.h"
0041 #include "profiles/profilemodel.hpp"
0042 #include "timeline2/view/qml/timelineitems.h"
0043 #include "timeline2/view/qmltypes/thumbnailprovider.h"
0044 #include <lib/localeHandling.h>
0045 #include <mlt++/Mlt.h>
0046 
0047 #ifndef GL_UNPACK_ROW_LENGTH
0048 #ifdef GL_UNPACK_ROW_LENGTH_EXT
0049 #define GL_UNPACK_ROW_LENGTH GL_UNPACK_ROW_LENGTH_EXT
0050 #else
0051 #error GL_UNPACK_ROW_LENGTH undefined
0052 #endif
0053 #endif
0054 
0055 #if 1
0056 #define check_error(fn)                                                                                                                                        \
0057     {                                                                                                                                                          \
0058     }
0059 #else
0060 #define check_error(fn)                                                                                                                                        \
0061     {                                                                                                                                                          \
0062         int err = fn->glGetError();                                                                                                                            \
0063         if (err != GL_NO_ERROR) {                                                                                                                              \
0064             qCritical(KDENLIVE_LOG) << "GL error" << hex << err << dec << "at" << __FILE__ << ":" << __LINE__;                                                 \
0065         }                                                                                                                                                      \
0066     }
0067 #endif
0068 
0069 #ifndef GL_TIMEOUT_IGNORED
0070 #define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
0071 #endif
0072 
0073 using namespace Mlt;
0074 
0075 VideoWidget::VideoWidget(int id, QWidget *parent)
0076     : QQuickWidget(parent)
0077     , sendFrameForAnalysis(false)
0078     , m_glslManager(nullptr)
0079     , m_consumer(nullptr)
0080     , m_producer(nullptr)
0081     , m_id(id)
0082     , m_rulerHeight(int(QFontInfo(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)).pixelSize() * 1.5))
0083     , m_bgColor(KdenliveSettings::window_background())
0084     , m_shader(nullptr)
0085     , m_initSem(0)
0086     , m_analyseSem(1)
0087     , m_isInitialized(false)
0088     , m_maxProducerPosition(0)
0089     , m_threadStartEvent(nullptr)
0090     , m_threadStopEvent(nullptr)
0091     , m_threadCreateEvent(nullptr)
0092     , m_threadJoinEvent(nullptr)
0093     , m_displayEvent(nullptr)
0094     , m_frameRenderer(nullptr)
0095     , m_projectionLocation(0)
0096     , m_modelViewLocation(0)
0097     , m_vertexLocation(0)
0098     , m_texCoordLocation(0)
0099     , m_colorspaceLocation(0)
0100     , m_zoom(1.0f)
0101     , m_profileSize(1920, 1080)
0102     , m_colorSpace(601)
0103     , m_dar(1.78)
0104     , m_sendFrame(false)
0105     , m_isZoneMode(false)
0106     , m_isLoopMode(false)
0107     , m_loopIn(0)
0108     , m_offset(QPoint(0, 0))
0109     , m_fbo(nullptr)
0110     , m_shareContext(nullptr)
0111     , m_openGLSync(false)
0112     , m_ClientWaitSync(nullptr)
0113 {
0114 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || KDECLARATIVE_VERSION > QT_VERSION_CHECK(5, 98, 0)
0115     engine()->addImageProvider(QStringLiteral("icon"), new KQuickIconProvider);
0116 #else
0117     KDeclarative::KDeclarative kdeclarative;
0118     kdeclarative.setDeclarativeEngine(engine());
0119     kdeclarative.setupEngine(engine());
0120 #endif
0121     engine()->rootContext()->setContextObject(new KLocalizedContext(this));
0122 
0123     m_texture[0] = m_texture[1] = m_texture[2] = 0;
0124     qRegisterMetaType<Mlt::Frame>("Mlt::Frame");
0125     qRegisterMetaType<SharedFrame>("SharedFrame");
0126     setAcceptDrops(true);
0127 
0128     if (m_id == Kdenlive::ClipMonitor && !(KdenliveSettings::displayClipMonitorInfo() & 0x01)) {
0129         m_rulerHeight = 0;
0130     } else if (!(KdenliveSettings::displayProjectMonitorInfo() & 0x01)) {
0131         m_rulerHeight = 0;
0132     }
0133     m_displayRulerHeight = m_rulerHeight;
0134 
0135 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0136     quickWindow()->setPersistentOpenGLContext(true);
0137     quickWindow()->setPersistentSceneGraph(true);
0138     quickWindow()->setClearBeforeRendering(false);
0139 #else
0140     // TODO: qt6
0141     quickWindow()->setPersistentGraphics(true);
0142     quickWindow()->setPersistentSceneGraph(true);
0143     //quickWindow()->setClearBeforeRendering(false);
0144 #endif
0145     setResizeMode(QQuickWidget::SizeRootObjectToView);
0146     auto fmt = QOpenGLContext::globalShareContext()->format();
0147     fmt.setDepthBufferSize(format().depthBufferSize());
0148     fmt.setStencilBufferSize(format().stencilBufferSize());
0149     m_offscreenSurface.setFormat(fmt);
0150     m_offscreenSurface.create();
0151 
0152     m_refreshTimer.setSingleShot(true);
0153     m_refreshTimer.setInterval(10);
0154     m_blackClip.reset(new Mlt::Producer(pCore->getProjectProfile(), "color:0"));
0155     m_blackClip->set("mlt_image_format", "rgba");
0156     m_blackClip->set("kdenlive:id", "black");
0157     m_blackClip->set("out", 3);
0158     connect(&m_refreshTimer, &QTimer::timeout, this, &VideoWidget::refresh);
0159     m_producer = m_blackClip;
0160     rootContext()->setContextProperty("markersModel", nullptr);
0161     if (!initGPUAccel()) {
0162         disableGPUAccel();
0163     }
0164 
0165     connect(quickWindow(), &QQuickWindow::sceneGraphInitialized, this, &VideoWidget::initializeGL, Qt::DirectConnection);
0166     connect(quickWindow(), &QQuickWindow::beforeRendering, this, &VideoWidget::paintGL, Qt::DirectConnection);
0167     // connect(pCore.get(), &Core::updateMonitorProfile, this, &VideoWidget::reloadProfile);
0168     connect(pCore.get(), &Core::switchTimelineRecord, this, &VideoWidget::switchRecordState);
0169 
0170     registerTimelineItems();
0171     m_proxy = new MonitorProxy(this);
0172     rootContext()->setContextProperty("controller", m_proxy);
0173     engine()->addImageProvider(QStringLiteral("thumbnail"), new ThumbnailProvider);
0174 }
0175 
0176 VideoWidget::~VideoWidget()
0177 {
0178     // C & D
0179     delete m_glslManager;
0180     delete m_threadStartEvent;
0181     delete m_threadStopEvent;
0182     delete m_threadCreateEvent;
0183     delete m_threadJoinEvent;
0184     delete m_displayEvent;
0185     if (m_frameRenderer) {
0186         if (m_frameRenderer->isRunning()) {
0187             QMetaObject::invokeMethod(m_frameRenderer, "cleanup");
0188             m_frameRenderer->quit();
0189             m_frameRenderer->wait();
0190             m_frameRenderer->deleteLater();
0191         } else {
0192             delete m_frameRenderer;
0193         }
0194     }
0195     m_blackClip.reset();
0196     delete m_shareContext;
0197     delete m_shader;
0198     // delete pCore->getCurrentProfile();
0199 }
0200 
0201 void VideoWidget::updateAudioForAnalysis()
0202 {
0203     if (m_frameRenderer) {
0204         m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
0205     }
0206 }
0207 
0208 void VideoWidget::initializeGL()
0209 {
0210     if (m_isInitialized) return;
0211 
0212 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0213     quickWindow()->openglContext()->makeCurrent(&m_offscreenSurface);
0214 #else
0215     QOpenGLContext &context = *static_cast< QOpenGLContext  *>(quickWindow()->rendererInterface()->getResource(quickWindow(), QSGRendererInterface::OpenGLContextResource));
0216     context.makeCurrent(&m_offscreenSurface);
0217 #endif
0218     initializeOpenGLFunctions();
0219 
0220     // C & D
0221     if (onlyGLESGPUAccel()) {
0222         disableGPUAccel();
0223     }
0224 
0225     createShader();
0226 
0227     m_openGLSync = initGPUAccelSync();
0228 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0229     quickWindow()->openglContext()->doneCurrent();
0230 #else
0231     context.doneCurrent();
0232 #endif
0233 
0234     // C & D
0235     if (m_glslManager) {
0236         // Create a context sharing with this context for the RenderThread context.
0237         // This is needed because openglContext() is active in another thread
0238         // at the time that RenderThread is created.
0239         // See this Qt bug for more info: https://bugreports.qt.io/browse/QTBUG-44677
0240         // TODO: QTBUG-44677 is closed. still applicable?
0241         m_shareContext = new QOpenGLContext;
0242 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0243         m_shareContext->setFormat(quickWindow()->openglContext()->format());
0244         m_shareContext->setShareContext(quickWindow()->openglContext());
0245 #else
0246         m_shareContext->setFormat(context.format());
0247         m_shareContext->setShareContext(&context);
0248 #endif
0249         m_shareContext->create();
0250     }
0251 
0252 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0253     m_frameRenderer = new FrameRenderer(quickWindow()->openglContext(), &m_offscreenSurface, m_ClientWaitSync);
0254 #else
0255     m_frameRenderer = new FrameRenderer(&context, &m_offscreenSurface, m_ClientWaitSync);
0256 #endif
0257 
0258     m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
0259 
0260 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0261     quickWindow()->openglContext()->makeCurrent(quickWindow());
0262 #else
0263     context.makeCurrent(quickWindow());
0264 #endif
0265     connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &VideoWidget::onFrameDisplayed, Qt::QueuedConnection);
0266     connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &VideoWidget::frameDisplayed, Qt::QueuedConnection);
0267     connect(m_frameRenderer, &FrameRenderer::textureReady, this, &VideoWidget::updateTexture, Qt::DirectConnection);
0268     m_initSem.release();
0269     m_isInitialized = true;
0270     QMetaObject::invokeMethod(this, "reconfigure", Qt::QueuedConnection);
0271 }
0272 
0273 void VideoWidget::resizeGL(int width, int height)
0274 {
0275     int x, y, w, h;
0276     height -= m_displayRulerHeight;
0277     double this_aspect = double(width) / height;
0278 
0279     // Special case optimization to negate odd effect of sample aspect ratio
0280     // not corresponding exactly with image resolution.
0281     if (int(this_aspect * 1000) == int(m_dar * 1000)) {
0282         w = width;
0283         h = height;
0284     }
0285     // Use OpenGL to normalise sample aspect ratio
0286     else if (height * m_dar > width) {
0287         w = width;
0288         h = int(width / m_dar);
0289     } else {
0290         w = int(height * m_dar);
0291         h = height;
0292     }
0293     x = (width - w) / 2;
0294     y = (height - h) / 2;
0295     m_rect.setRect(x, y, w, h);
0296     QQuickItem *rootQml = rootObject();
0297     if (rootQml) {
0298         QSize s = pCore->getCurrentFrameSize();
0299         double scalex = double(m_rect.width() * m_zoom) / s.width();
0300         double scaley = double(m_rect.height() * m_zoom) / s.height();
0301         rootQml->setProperty("center", m_rect.center());
0302         rootQml->setProperty("scalex", scalex);
0303         rootQml->setProperty("scaley", scaley);
0304         if (rootQml->objectName() == QLatin1String("rootsplit")) {
0305             // Adjust splitter pos
0306             rootQml->setProperty("splitterPos", x + (rootQml->property("percentage").toDouble() * w));
0307         }
0308     }
0309     Q_EMIT rectChanged();
0310 }
0311 
0312 void VideoWidget::resizeEvent(QResizeEvent *event)
0313 {
0314     QQuickWidget::resizeEvent(event);
0315     resizeGL(event->size().width(), event->size().height());
0316 }
0317 
0318 void VideoWidget::createGPUAccelFragmentProg()
0319 {
0320     m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D tex;"
0321                                                                "varying highp vec2 coordinates;"
0322                                                                "void main(void) {"
0323                                                                "  gl_FragColor = texture2D(tex, coordinates);"
0324                                                                "}");
0325     m_shader->link();
0326     m_textureLocation[0] = m_shader->uniformLocation("tex");
0327 }
0328 
0329 void VideoWidget::createShader()
0330 {
0331     m_shader = new QOpenGLShaderProgram;
0332     m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, "uniform highp mat4 projection;"
0333                                                              "uniform highp mat4 modelView;"
0334                                                              "attribute highp vec4 vertex;"
0335                                                              "attribute highp vec2 texCoord;"
0336                                                              "varying highp vec2 coordinates;"
0337                                                              "void main(void) {"
0338                                                              "  gl_Position = projection * modelView * vertex;"
0339                                                              "  coordinates = texCoord;"
0340                                                              "}");
0341     // C & D
0342     if (m_glslManager) {
0343         createGPUAccelFragmentProg();
0344     } else {
0345         // A & B
0346         createYUVTextureProjectFragmentProg();
0347     }
0348 
0349     m_projectionLocation = m_shader->uniformLocation("projection");
0350     m_modelViewLocation = m_shader->uniformLocation("modelView");
0351     m_vertexLocation = m_shader->attributeLocation("vertex");
0352     m_texCoordLocation = m_shader->attributeLocation("texCoord");
0353 }
0354 
0355 void VideoWidget::createYUVTextureProjectFragmentProg()
0356 {
0357     m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,
0358                                       "uniform sampler2D Ytex, Utex, Vtex;"
0359                                       "uniform lowp int colorspace;"
0360                                       "varying highp vec2 coordinates;"
0361                                       "void main(void) {"
0362                                       "  mediump vec3 texel;"
0363                                       "  texel.r = texture2D(Ytex, coordinates).r - 16.0/255.0;"  // Y
0364                                       "  texel.g = texture2D(Utex, coordinates).r - 128.0/255.0;" // U
0365                                       "  texel.b = texture2D(Vtex, coordinates).r - 128.0/255.0;" // V
0366                                       "  mediump mat3 coefficients;"
0367                                       "  if (colorspace == 601) {"
0368                                       "    coefficients = mat3("
0369                                       "      1.1643,  1.1643,  1.1643," // column 1
0370                                       "      0.0,    -0.39173, 2.017,"  // column 2
0371                                       "      1.5958, -0.8129,  0.0);"   // column 3
0372                                       "  } else {"                      // ITU-R 709
0373                                       "    coefficients = mat3("
0374                                       "      1.1643, 1.1643, 1.1643," // column 1
0375                                       "      0.0,   -0.213,  2.112,"  // column 2
0376                                       "      1.793, -0.533,  0.0);"   // column 3
0377                                       "  }"
0378                                       "  gl_FragColor = vec4(coefficients * texel, 1.0);"
0379                                       "}");
0380     m_shader->link();
0381     m_textureLocation[0] = m_shader->uniformLocation("Ytex");
0382     m_textureLocation[1] = m_shader->uniformLocation("Utex");
0383     m_textureLocation[2] = m_shader->uniformLocation("Vtex");
0384     m_colorspaceLocation = m_shader->uniformLocation("colorspace");
0385 }
0386 
0387 static void uploadTextures(QOpenGLContext *context, const SharedFrame &frame, GLuint texture[])
0388 {
0389     int width = frame.get_image_width();
0390     int height = frame.get_image_height();
0391     const uint8_t *image = frame.get_image(mlt_image_yuv420p);
0392     QOpenGLFunctions *f = context->functions();
0393 
0394     // The planes of pixel data may not be a multiple of the default 4 bytes.
0395     f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
0396 
0397     // Upload each plane of YUV to a texture.
0398     if (texture[0] != 0u) {
0399         f->glDeleteTextures(3, texture);
0400     }
0401     check_error(f);
0402     f->glGenTextures(3, texture);
0403     check_error(f);
0404 
0405     f->glBindTexture(GL_TEXTURE_2D, texture[0]);
0406     check_error(f);
0407     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0408     check_error(f);
0409     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0410     check_error(f);
0411     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0412     check_error(f);
0413     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0414     check_error(f);
0415     f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
0416     check_error(f);
0417 
0418     f->glBindTexture(GL_TEXTURE_2D, texture[1]);
0419     check_error(f);
0420     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0421     check_error(f);
0422     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0423     check_error(f);
0424     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0425     check_error(f);
0426     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0427     check_error(f);
0428     f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height);
0429     check_error(f);
0430 
0431     f->glBindTexture(GL_TEXTURE_2D, texture[2]);
0432     check_error(f);
0433     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0434     check_error(f);
0435     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0436     check_error(f);
0437     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0438     check_error(f);
0439     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0440     check_error(f);
0441     f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height + width / 2 * height / 2);
0442     check_error(f);
0443 }
0444 
0445 void VideoWidget::clear()
0446 {
0447     stopGlsl();
0448     quickWindow()->update();
0449 }
0450 
0451 void VideoWidget::releaseAnalyse()
0452 {
0453     m_analyseSem.release();
0454 }
0455 
0456 bool VideoWidget::acquireSharedFrameTextures()
0457 {
0458     // A
0459 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0460     if ((m_glslManager == nullptr) && !quickWindow()->openglContext()->supportsThreadedOpenGL()) {
0461         QMutexLocker locker(&m_contextSharedAccess);
0462         if (!m_sharedFrame.is_valid()) {
0463             return false;
0464         }
0465         uploadTextures(quickWindow()->openglContext(), m_sharedFrame, m_texture);
0466     } else
0467 #else
0468         QOpenGLContext &context = *static_cast< QOpenGLContext  *>(quickWindow()->rendererInterface()->getResource(quickWindow(), QSGRendererInterface::OpenGLContextResource));
0469         if ((m_glslManager == nullptr) && !context.supportsThreadedOpenGL()) {
0470             QMutexLocker locker(&m_contextSharedAccess);
0471             if (!m_sharedFrame.is_valid()) {
0472                 return false;
0473             }
0474             uploadTextures(&context, m_sharedFrame, m_texture);
0475         } else
0476 #endif
0477     if (m_glslManager) {
0478         // C & D
0479         m_contextSharedAccess.lock();
0480         if (m_sharedFrame.is_valid()) {
0481             m_texture[0] = *(reinterpret_cast<const GLuint *>(m_sharedFrame.get_image(mlt_image_opengl_texture)));
0482         }
0483     }
0484 
0485     if (!m_texture[0]) {
0486         // C & D
0487         if (m_glslManager) m_contextSharedAccess.unlock();
0488         return false;
0489     }
0490 
0491     return true;
0492 }
0493 
0494 void VideoWidget::bindShaderProgram()
0495 {
0496     m_shader->bind();
0497 
0498     // C & D
0499     if (m_glslManager) {
0500         m_shader->setUniformValue(m_textureLocation[0], 0);
0501     } else {
0502         // A & B
0503         m_shader->setUniformValue(m_textureLocation[0], 0);
0504         m_shader->setUniformValue(m_textureLocation[1], 1);
0505         m_shader->setUniformValue(m_textureLocation[2], 2);
0506         m_shader->setUniformValue(m_colorspaceLocation, m_colorSpace);
0507     }
0508 }
0509 
0510 void VideoWidget::releaseSharedFrameTextures()
0511 {
0512     // C & D
0513     if (m_glslManager) {
0514         glFinish();
0515         m_contextSharedAccess.unlock();
0516     }
0517 }
0518 
0519 bool VideoWidget::initGPUAccel()
0520 {
0521     if (!KdenliveSettings::gpu_accel()) return false;
0522 
0523     m_glslManager = new Mlt::Filter(pCore->getProjectProfile(), "glsl.manager");
0524     return m_glslManager->is_valid();
0525 }
0526 
0527 // C & D
0528 // TODO: insure safe, idempotent on all pipelines.
0529 void VideoWidget::disableGPUAccel()
0530 {
0531     delete m_glslManager;
0532     m_glslManager = nullptr;
0533     KdenliveSettings::setGpu_accel(false);
0534     // Need to destroy MLT global reference to prevent filters from trying to use GPU.
0535     mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr);
0536     Q_EMIT gpuNotSupported();
0537 }
0538 
0539 bool VideoWidget::onlyGLESGPUAccel() const
0540 {
0541 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0542     return (m_glslManager != nullptr) && quickWindow()->openglContext()->isOpenGLES();
0543 #else
0544     QOpenGLContext &context = *static_cast< QOpenGLContext  *>(quickWindow()->rendererInterface()->getResource(quickWindow(), QSGRendererInterface::OpenGLContextResource));
0545     return (m_glslManager != nullptr) && context.isOpenGLES();
0546 #endif
0547 }
0548 
0549 #if defined(Q_OS_WIN)
0550 bool VideoWidget::initGPUAccelSync()
0551 {
0552     // no-op
0553     // TODO: getProcAddress is not working on Windows?
0554     return false;
0555 }
0556 #else
0557 bool VideoWidget::initGPUAccelSync()
0558 {
0559     if (!KdenliveSettings::gpu_accel()) return false;
0560     if (m_glslManager == nullptr) return false;
0561 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0562     if (!quickWindow()->openglContext()->hasExtension("GL_ARB_sync")) return false;
0563 
0564     m_ClientWaitSync = ClientWaitSync_fp(quickWindow()->openglContext()->getProcAddress("glClientWaitSync"));
0565 #else
0566     QOpenGLContext &context = *static_cast< QOpenGLContext  *>(quickWindow()->rendererInterface()->getResource(quickWindow(), QSGRendererInterface::OpenGLContextResource));
0567     if (!context.hasExtension("GL_ARB_sync")) return false;
0568 
0569     m_ClientWaitSync = ClientWaitSync_fp(context.getProcAddress("glClientWaitSync"));
0570 #endif
0571     if (m_ClientWaitSync) {
0572         return true;
0573     } else {
0574         qWarning() << "no GL sync";
0575         // fallback on A || B
0576         // TODO: fallback on A || B || C?
0577         disableGPUAccel();
0578         return false;
0579     }
0580 }
0581 #endif
0582 
0583 void VideoWidget::paintGL()
0584 {
0585 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0586     QOpenGLFunctions *f = quickWindow()->openglContext()->functions();
0587 #else
0588     QOpenGLContext &context = *static_cast< QOpenGLContext  *>(quickWindow()->rendererInterface()->getResource(quickWindow(), QSGRendererInterface::OpenGLContextResource));
0589     QOpenGLFunctions *f = context.functions();
0590 #endif
0591 
0592     float width = this->width() * devicePixelRatioF();
0593     float height = this->height() * devicePixelRatioF();
0594     f->glClearColor(float(m_bgColor.redF()), float(m_bgColor.greenF()), float(m_bgColor.blueF()), 1);
0595     f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
0596     f->glDisable(GL_BLEND);
0597     f->glDisable(GL_DEPTH_TEST);
0598     f->glDepthMask(GL_FALSE);
0599     f->glViewport(0, qRound(m_displayRulerHeight * devicePixelRatioF() * 0.5), int(width), int(height));
0600     check_error(f);
0601 
0602     if (!acquireSharedFrameTextures()) return;
0603 
0604     // Bind textures.
0605     for (uint i = 0; i < 3; ++i) {
0606         if (m_texture[i] != 0u) {
0607             f->glActiveTexture(GL_TEXTURE0 + i);
0608             f->glBindTexture(GL_TEXTURE_2D, m_texture[i]);
0609             check_error(f);
0610         }
0611     }
0612 
0613     bindShaderProgram();
0614     check_error(f);
0615 
0616     // Setup an orthographic projection.
0617     QMatrix4x4 projection;
0618     projection.scale(2.0f / width, 2.0f / height);
0619     m_shader->setUniformValue(m_projectionLocation, projection);
0620     check_error(f);
0621 
0622     // Set model view.
0623     QMatrix4x4 modelView;
0624     if (!qFuzzyCompare(m_zoom, 1.0f)) {
0625         if ((offset().x() != 0) || (offset().y() != 0)) modelView.translate(-offset().x() * devicePixelRatioF(), offset().y() * devicePixelRatioF());
0626         modelView.scale(zoom(), zoom());
0627     }
0628     m_shader->setUniformValue(m_modelViewLocation, modelView);
0629     check_error(f);
0630 
0631     // Provide vertices of triangle strip.
0632     QVector<QVector2D> vertices;
0633     width = m_rect.width() * devicePixelRatioF();
0634     height = m_rect.height() * devicePixelRatioF();
0635     vertices << QVector2D(-width / 2.0f, -height / 2.0f);
0636     vertices << QVector2D(-width / 2.0f, height / 2.0f);
0637     vertices << QVector2D(width / 2.0f, -height / 2.0f);
0638     vertices << QVector2D(width / 2.0f, height / 2.0f);
0639     m_shader->enableAttributeArray(m_vertexLocation);
0640     check_error(f);
0641     m_shader->setAttributeArray(m_vertexLocation, vertices.constData());
0642     check_error(f);
0643 
0644     // Provide texture coordinates.
0645     QVector<QVector2D> texCoord;
0646     texCoord << QVector2D(0.0f, 1.0f);
0647     texCoord << QVector2D(0.0f, 0.0f);
0648     texCoord << QVector2D(1.0f, 1.0f);
0649     texCoord << QVector2D(1.0f, 0.0f);
0650     m_shader->enableAttributeArray(m_texCoordLocation);
0651     check_error(f);
0652     m_shader->setAttributeArray(m_texCoordLocation, texCoord.constData());
0653     check_error(f);
0654 
0655     // Render
0656     glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
0657     check_error(f);
0658 
0659     if (m_sendFrame && m_analyseSem.tryAcquire(1)) {
0660         // Render RGB frame for analysis
0661         if (!qFuzzyCompare(m_zoom, 1.0f)) {
0662             // Disable monitor zoom to render frame
0663             modelView = QMatrix4x4();
0664             m_shader->setUniformValue(m_modelViewLocation, modelView);
0665         }
0666         if ((m_fbo == nullptr) || m_fbo->size() != m_profileSize) {
0667             delete m_fbo;
0668             QOpenGLFramebufferObjectFormat fmt;
0669             fmt.setSamples(1);
0670             m_fbo = new QOpenGLFramebufferObject(m_profileSize.width(), m_profileSize.height(), fmt); // GL_TEXTURE_2D);
0671         }
0672         m_fbo->bind();
0673         glViewport(0, 0, m_profileSize.width(), m_profileSize.height());
0674 
0675         QMatrix4x4 projection2;
0676         projection2.scale(2.0f / width, 2.0f / height);
0677         m_shader->setUniformValue(m_projectionLocation, projection2);
0678 
0679         glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
0680         check_error(f);
0681         m_fbo->release();
0682         Q_EMIT analyseFrame(m_fbo->toImage());
0683         m_sendFrame = false;
0684     }
0685     // Cleanup
0686     m_shader->disableAttributeArray(m_vertexLocation);
0687     m_shader->disableAttributeArray(m_texCoordLocation);
0688     m_shader->release();
0689     for (uint i = 0; i < 3; ++i) {
0690         if (m_texture[i] != 0u) {
0691             f->glActiveTexture(GL_TEXTURE0 + i);
0692             f->glBindTexture(GL_TEXTURE_2D, 0);
0693             check_error(f);
0694         }
0695     }
0696     glActiveTexture(GL_TEXTURE0);
0697     check_error(f);
0698 
0699     releaseSharedFrameTextures();
0700     check_error(f);
0701 }
0702 
0703 void VideoWidget::slotZoom(bool zoomIn)
0704 {
0705     if (zoomIn) {
0706         if (m_zoom < 12.0f) {
0707             setZoom(m_zoom * 1.2);
0708         }
0709     } else if (m_zoom > 0.2f) {
0710         setZoom(m_zoom / 1.2f);
0711     }
0712 }
0713 
0714 void VideoWidget::updateRulerHeight(int addedHeight)
0715 {
0716     m_displayRulerHeight =
0717         m_rulerHeight > 0 ? int(QFontInfo(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)).pixelSize() * 1.5) + addedHeight : 0;
0718     resizeGL(width(), height());
0719 }
0720 
0721 bool VideoWidget::isReady() const
0722 {
0723     return m_consumer != nullptr;
0724 }
0725 
0726 void VideoWidget::requestSeek(int position, bool noAudioScrub)
0727 {
0728     m_producer->seek(position);
0729     if (!qFuzzyIsNull(m_producer->get_speed())) {
0730         m_consumer->purge();
0731     }
0732     restartConsumer();
0733     m_consumer->set("refresh", 1);
0734     if (KdenliveSettings::audio_scrub() && !noAudioScrub) {
0735         m_consumer->set("scrub_audio", 1);
0736     } else {
0737         m_consumer->set("scrub_audio", 0);
0738     }
0739 }
0740 
0741 void VideoWidget::requestRefresh()
0742 {
0743     if (m_producer && qFuzzyIsNull(m_producer->get_speed())) {
0744         m_consumer->set("scrub_audio", 0);
0745         m_refreshTimer.start();
0746     }
0747 }
0748 
0749 QString VideoWidget::frameToTime(int frames) const
0750 {
0751     return m_consumer ? m_consumer->frames_to_time(frames, mlt_time_smpte_df) : QStringLiteral("-");
0752 }
0753 
0754 void VideoWidget::refresh()
0755 {
0756     m_refreshTimer.stop();
0757     QMutexLocker locker(&m_mltMutex);
0758     if (m_consumer) {
0759         restartConsumer();
0760         m_consumer->set("refresh", 1);
0761     }
0762 }
0763 
0764 bool VideoWidget::checkFrameNumber(int pos, bool isPlaying)
0765 {
0766     const double speed = m_producer->get_speed();
0767     m_proxy->positionFromConsumer(pos, isPlaying);
0768     if (m_isLoopMode || m_isZoneMode) {
0769         // not sure why we need to check against pos + 1 but otherwise the
0770         // playback shows one frame after the intended out frame
0771         if (isPlaying && pos + 1 >= m_loopOut) {
0772             m_consumer->purge();
0773             if (!m_isLoopMode) {
0774                 // end play zone mode
0775                 m_isZoneMode = false;
0776                 m_producer->set_speed(0);
0777                 m_proxy->setSpeed(0);
0778                 m_consumer->set("refresh", 0);
0779                 m_proxy->setPosition(m_loopOut);
0780                 m_producer->seek(m_loopOut);
0781                 m_loopOut = 0;
0782                 return false;
0783             }
0784             m_producer->seek(m_isZoneMode ? m_proxy->zoneIn() : m_loopIn);
0785             m_producer->set_speed(1.0);
0786             m_proxy->setSpeed(1.);
0787             m_consumer->set("refresh", 1);
0788             return true;
0789         }
0790         return true;
0791     } else if (isPlaying) {
0792         if (pos > m_maxProducerPosition - 2 && !(speed < 0.)) {
0793             // Playing past last clip, pause
0794             m_producer->set_speed(0);
0795             m_proxy->setSpeed(0);
0796             m_consumer->set("refresh", 0);
0797             m_consumer->purge();
0798             m_proxy->setPosition(qMax(0, m_maxProducerPosition));
0799             m_producer->seek(qMax(0, m_maxProducerPosition));
0800             return false;
0801         } else if (pos <= 0 && speed < 0.) {
0802             // rewinding reached 0, pause
0803             m_producer->set_speed(0);
0804             m_proxy->setSpeed(0);
0805             m_consumer->set("refresh", 0);
0806             m_consumer->purge();
0807             m_proxy->setPosition(0);
0808             m_producer->seek(0);
0809             return false;
0810         }
0811     }
0812     return isPlaying;
0813 }
0814 
0815 void VideoWidget::mousePressEvent(QMouseEvent *event)
0816 {
0817     if ((rootObject() != nullptr) && rootObject()->property("captureRightClick").toBool() && !(event->modifiers() & Qt::ControlModifier) &&
0818         !(event->buttons() & Qt::MiddleButton)) {
0819         event->ignore();
0820         QQuickWidget::mousePressEvent(event);
0821         return;
0822     }
0823     QQuickWidget::mousePressEvent(event);
0824     event->accept();
0825     if ((event->button() & Qt::LeftButton) != 0u) {
0826         if ((event->modifiers() & Qt::ControlModifier) != 0u) {
0827             // Pan view
0828             m_panStart = event->pos();
0829             setCursor(Qt::ClosedHandCursor);
0830         } else {
0831             m_dragStart = event->pos();
0832         }
0833     } else if ((event->button() & Qt::RightButton) != 0u) {
0834 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0835         Q_EMIT showContextMenu(event->globalPos());
0836 #else
0837         Q_EMIT showContextMenu(event->globalPosition().toPoint());
0838 #endif
0839     } else if ((event->button() & Qt::MiddleButton) != 0u) {
0840         m_panStart = event->pos();
0841         setCursor(Qt::ClosedHandCursor);
0842     }
0843 }
0844 
0845 void VideoWidget::mouseMoveEvent(QMouseEvent *event)
0846 {
0847     if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
0848         !(event->buttons() & Qt::MiddleButton)) {
0849         event->ignore();
0850         QQuickWidget::mouseMoveEvent(event);
0851         return;
0852     }
0853     QQuickWidget::mouseMoveEvent(event);
0854     if (!(event->buttons() & Qt::LeftButton)) {
0855         event->accept();
0856         return;
0857     }
0858     if (!m_panStart.isNull()) {
0859         Q_EMIT panView(m_panStart - event->pos());
0860         m_panStart = event->pos();
0861         event->accept();
0862         return;
0863     }
0864 
0865     if (!event->isAccepted() && !m_dragStart.isNull() && (event->pos() - m_dragStart).manhattanLength() >= QApplication::startDragDistance()) {
0866         m_dragStart = QPoint();
0867         Q_EMIT startDrag();
0868     }
0869     event->accept();
0870 }
0871 
0872 void VideoWidget::keyPressEvent(QKeyEvent *event)
0873 {
0874     QQuickWidget::keyPressEvent(event);
0875     if (!event->isAccepted()) {
0876         Q_EMIT passKeyEvent(event);
0877     }
0878 }
0879 
0880 void VideoWidget::createThread(RenderThread **thread, thread_function_t function, void *data)
0881 {
0882 #ifdef Q_OS_WIN
0883     // On Windows, MLT event consumer-thread-create is fired from the Qt main thread.
0884     while (!m_isInitialized) {
0885         qApp->processEvents();
0886     }
0887 #else
0888     if (!m_isInitialized) {
0889         m_initSem.acquire();
0890     }
0891 #endif
0892     (*thread) = new RenderThread(function, data, m_shareContext, &m_offscreenSurface);
0893     (*thread)->start();
0894 }
0895 
0896 static void onThreadCreate(mlt_properties owner, VideoWidget *self, mlt_event_data data)
0897 {
0898     Q_UNUSED(owner)
0899     auto threadData = (mlt_event_data_thread *)Mlt::EventData(data).to_object();
0900     if (threadData) {
0901         auto renderThread = (RenderThread *)threadData->thread;
0902         self->createThread(&renderThread, threadData->function, threadData->data);
0903         // TODO: useless ?
0904         // self->lockMonitor();
0905     }
0906 }
0907 
0908 static void onThreadJoin(mlt_properties owner, VideoWidget *self, mlt_event_data data)
0909 {
0910     Q_UNUSED(owner)
0911     Q_UNUSED(self)
0912     auto threadData = (mlt_event_data_thread *)Mlt::EventData(data).to_object();
0913     if (threadData) {
0914         auto renderThread = (RenderThread *)threadData->thread;
0915         if (renderThread) {
0916             renderThread->quit();
0917             renderThread->wait();
0918             delete renderThread;
0919             // TODO: useless ?
0920             // self->releaseMonitor();
0921         }
0922     }
0923 }
0924 
0925 void VideoWidget::startGlsl()
0926 {
0927     // C & D
0928     if (m_glslManager) {
0929         // clearFrameRenderer();
0930         m_glslManager->fire_event("init glsl");
0931         if (m_glslManager->get_int("glsl_supported") == 0) {
0932             disableGPUAccel();
0933         } else {
0934             Q_EMIT started();
0935         }
0936     }
0937 }
0938 
0939 static void onThreadStarted(mlt_properties owner, VideoWidget *self, mlt_event_data)
0940 {
0941     Q_UNUSED(owner)
0942     self->startGlsl();
0943 }
0944 
0945 void VideoWidget::releaseMonitor()
0946 {
0947     Q_EMIT lockMonitor(false);
0948 }
0949 
0950 void VideoWidget::lockMonitor()
0951 {
0952     Q_EMIT lockMonitor(true);
0953 }
0954 
0955 void VideoWidget::stopGlsl()
0956 {
0957     if (m_consumer) {
0958         m_consumer->purge();
0959     }
0960 
0961     // C & D
0962     // TODO This is commented out for now because it is causing crashes.
0963     // Technically, this should be the correct thing to do, but it appears
0964     // some changes have created regression (see shotcut)
0965     // with respect to restarting the consumer in GPU mode.
0966     // m_glslManager->fire_event("close glsl");
0967     m_texture[0] = 0;
0968 }
0969 
0970 static void onThreadStopped(mlt_properties owner, VideoWidget *self, mlt_event_data)
0971 {
0972     Q_UNUSED(owner)
0973     self->stopGlsl();
0974 }
0975 
0976 int VideoWidget::setProducer(const QString &file)
0977 {
0978     if (m_producer) {
0979         m_producer.reset();
0980     }
0981     m_producer = std::make_shared<Mlt::Producer>(new Mlt::Producer(pCore->getProjectProfile(), nullptr, file.toUtf8().constData()));
0982     if (!m_producer || !m_producer->is_valid()) {
0983         m_producer.reset();
0984         m_producer = m_blackClip;
0985     }
0986     if (m_consumer) {
0987         // m_consumer->stop();
0988         if (!m_consumer->is_stopped()) {
0989             m_consumer->stop();
0990         }
0991     }
0992     int error = reconfigure();
0993     if (error == 0) {
0994         // The profile display aspect ratio may have changed.
0995         resizeGL(width(), height());
0996         startConsumer();
0997     }
0998     return error;
0999 }
1000 
1001 int VideoWidget::setProducer(const std::shared_ptr<Mlt::Producer> &producer, bool isActive, int position)
1002 {
1003     int error = 0;
1004     QString currentId;
1005     int consumerPosition = 0;
1006     if (m_producer) {
1007         currentId = m_producer->parent().get("kdenlive:id");
1008     }
1009     if (m_consumer) {
1010         consumerPosition = m_consumer->position();
1011     }
1012     stop();
1013     if (producer) {
1014         m_producer = producer;
1015     } else {
1016         if (currentId == QLatin1String("black")) {
1017             return 0;
1018         }
1019         m_producer = m_blackClip;
1020         // Reset markersModel
1021         rootContext()->setContextProperty("markersModel", nullptr);
1022     }
1023     m_producer->set_speed(0);
1024     m_proxy->setSpeed(0);
1025     error = reconfigure();
1026     if (error == 0) {
1027         // The profile display aspect ratio may have changed.
1028         resizeGL(width(), height());
1029     } else {
1030         return error;
1031     }
1032     if (!m_consumer) {
1033         return error;
1034     }
1035     if (position == -1 && m_producer->parent().get("kdenlive:id") == currentId) {
1036         position = consumerPosition;
1037     }
1038     if (isActive) {
1039         startConsumer();
1040         if (position != -2) {
1041             m_proxy->resetPosition();
1042         }
1043     }
1044     m_consumer->set("scrub_audio", 0);
1045     if (position != -2) {
1046         m_proxy->setPositionAdvanced(position > 0 ? position : m_producer->position(), true);
1047     }
1048     return error;
1049 }
1050 
1051 int VideoWidget::droppedFrames() const
1052 {
1053     return (m_consumer ? m_consumer->get_int("drop_count") : 0);
1054 }
1055 
1056 void VideoWidget::resetDrops()
1057 {
1058     if (m_consumer) {
1059         m_consumer->set("drop_count", 0);
1060     }
1061 }
1062 
1063 void VideoWidget::stopCapture()
1064 {
1065     if (strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
1066         m_consumer->set("refresh", 0);
1067         m_consumer->purge();
1068         m_consumer->stop();
1069     }
1070 }
1071 
1072 int VideoWidget::reconfigure()
1073 {
1074     int error = 0;
1075     // use SDL for audio, OpenGL for video
1076     QString serviceName = property("mlt_service").toString();
1077     if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
1078         if (m_consumer) {
1079             m_consumer->purge();
1080             m_consumer->stop();
1081             m_consumer.reset();
1082         }
1083         QString audioBackend = (KdenliveSettings::external_display()) ? QString("decklink:%1").arg(KdenliveSettings::blackmagic_output_device())
1084                                                                       : KdenliveSettings::audiobackend();
1085         if (m_consumer == nullptr || serviceName.isEmpty() || serviceName != audioBackend) {
1086             m_consumer.reset(new Mlt::FilteredConsumer(pCore->getMonitorProfile(), audioBackend.toLatin1().constData()));
1087             if (m_consumer->is_valid()) {
1088                 serviceName = audioBackend;
1089             } else {
1090                 // Warning, audio backend unavailable on system
1091                 m_consumer.reset();
1092                 QStringList backends = {"sdl2_audio", "sdl_audio", "rtaudio"};
1093                 for (const QString &bk : backends) {
1094                     if (bk == audioBackend) {
1095                         // Already tested
1096                         continue;
1097                     }
1098                     m_consumer.reset(new Mlt::FilteredConsumer(pCore->getMonitorProfile(), bk.toLatin1().constData()));
1099                     if (m_consumer->is_valid()) {
1100                         if (audioBackend == KdenliveSettings::sdlAudioBackend()) {
1101                             // switch sdl audio backend
1102                             KdenliveSettings::setSdlAudioBackend(bk);
1103                         }
1104                         KdenliveSettings::setAudiobackend(bk);
1105                         serviceName = bk;
1106                         break;
1107                     } else {
1108                         m_consumer.reset();
1109                     }
1110                 }
1111             }
1112             if (!m_consumer || !m_consumer->is_valid()) {
1113                 qWarning() << "no audio backend found";
1114                 return -1;
1115             }
1116             setProperty("mlt_service", serviceName);
1117             if (KdenliveSettings::external_display()) {
1118                 m_consumer->set("terminate_on_pause", 0);
1119             }
1120             m_consumer->set("width", m_profileSize.width());
1121             m_consumer->set("height", m_profileSize.height());
1122             m_colorSpace = pCore->getCurrentProfile()->colorspace();
1123             m_dar = pCore->getCurrentDar();
1124         }
1125         delete m_threadStartEvent;
1126         m_threadStartEvent = nullptr;
1127         delete m_threadStopEvent;
1128         m_threadStopEvent = nullptr;
1129 
1130         delete m_threadCreateEvent;
1131         delete m_threadJoinEvent;
1132         if (m_glslManager) {
1133             m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, mlt_listener(onThreadCreate));
1134             m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, mlt_listener(onThreadJoin));
1135         }
1136     }
1137     if (m_consumer->is_valid()) {
1138         // Connect the producer to the consumer - tell it to "run" later
1139         if (m_producer) {
1140             m_consumer->connect(*m_producer.get());
1141             // m_producer->set_speed(0.0);
1142         }
1143 
1144         int dropFrames = 1;
1145         if (!KdenliveSettings::monitor_dropframes()) {
1146             dropFrames = -dropFrames;
1147         }
1148         m_consumer->set("real_time", dropFrames);
1149         m_consumer->set("channels", pCore->audioChannels());
1150         if (KdenliveSettings::previewScaling() > 1) {
1151             m_consumer->set("scale", 1.0 / KdenliveSettings::previewScaling());
1152         }
1153         // C & D
1154         if (m_glslManager) {
1155             if (!m_threadStartEvent) {
1156                 m_threadStartEvent = m_consumer->listen("consumer-thread-started", this, mlt_listener(onThreadStarted));
1157             }
1158             if (!m_threadStopEvent) {
1159                 m_threadStopEvent = m_consumer->listen("consumer-thread-stopped", this, mlt_listener(onThreadStopped));
1160             }
1161             if (!serviceName.startsWith(QLatin1String("decklink"))) {
1162                 m_consumer->set("mlt_image_format", "glsl");
1163             }
1164         } else {
1165             // A & B
1166             m_consumer->set("mlt_image_format", "yuv422");
1167         }
1168 
1169         delete m_displayEvent;
1170         // C & D
1171         if (m_glslManager) {
1172             m_displayEvent = m_consumer->listen("consumer-frame-show", this, mlt_listener(on_gl_frame_show));
1173         } else {
1174             // A & B
1175             m_displayEvent = m_consumer->listen("consumer-frame-show", this, mlt_listener(on_frame_show));
1176         }
1177 
1178         int volume = KdenliveSettings::volume();
1179         if (serviceName.startsWith(QLatin1String("sdl"))) {
1180             QString audioDevice = KdenliveSettings::audiodevicename();
1181             if (!audioDevice.isEmpty()) {
1182                 m_consumer->set("audio_device", audioDevice.toUtf8().constData());
1183             }
1184 
1185             QString audioDriver = KdenliveSettings::audiodrivername();
1186             if (!audioDriver.isEmpty()) {
1187                 m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
1188             }
1189         }
1190         if (!pCore->getProjectProfile().progressive()) {
1191             m_consumer->set("progressive", KdenliveSettings::monitor_progressive());
1192         }
1193         m_consumer->set("volume", volume / 100.0);
1194         // m_consumer->set("progressive", 1);
1195         m_consumer->set("rescale", KdenliveSettings::mltinterpolation().toUtf8().constData());
1196         m_consumer->set("deinterlacer", KdenliveSettings::mltdeinterlacer().toUtf8().constData());
1197         /*
1198 #ifdef Q_OS_WIN
1199         m_consumer->set("audio_buffer", 2048);
1200 #else
1201         m_consumer->set("audio_buffer", 512);
1202 #endif
1203         */
1204         int fps = qRound(pCore->getCurrentFps());
1205         m_consumer->set("buffer", qMax(25, fps));
1206         m_consumer->set("prefill", 6);
1207         m_consumer->set("drop_max", fps / 4);
1208         if (KdenliveSettings::audio_scrub()) {
1209             m_consumer->set("scrub_audio", 1);
1210         } else {
1211             m_consumer->set("scrub_audio", 0);
1212         }
1213         if (KdenliveSettings::monitor_gamma() == 0) {
1214             m_consumer->set("color_trc", "iec61966_2_1");
1215         } else {
1216             m_consumer->set("color_trc", "bt709");
1217         }
1218     } else {
1219         // Cleanup on error
1220         error = 2;
1221     }
1222     return error;
1223 }
1224 
1225 float VideoWidget::zoom() const
1226 {
1227     return m_zoom;
1228 }
1229 
1230 void VideoWidget::reloadProfile()
1231 {
1232     // The profile display aspect ratio may have changed.
1233     bool existingConsumer = false;
1234     if (m_consumer) {
1235         // Make sure to delete and rebuild consumer to match profile
1236         m_consumer->purge();
1237         m_consumer->stop();
1238         m_consumer.reset();
1239         existingConsumer = true;
1240     }
1241     m_blackClip.reset(new Mlt::Producer(pCore->getProjectProfile(), "color:0"));
1242     m_blackClip->set("kdenlive:id", "black");
1243     m_blackClip->set("mlt_image_format", "rgba");
1244     if (existingConsumer) {
1245         reconfigure();
1246     }
1247     resizeGL(width(), height());
1248     refreshSceneLayout();
1249 }
1250 
1251 QSize VideoWidget::profileSize() const
1252 {
1253     return m_profileSize;
1254 }
1255 
1256 QRect VideoWidget::displayRect() const
1257 {
1258     return m_rect;
1259 }
1260 
1261 QPoint VideoWidget::offset() const
1262 {
1263     return {m_offset.x() - static_cast<int>(width() * m_zoom / 2), m_offset.y() - static_cast<int>(height() * m_zoom / 2)};
1264 }
1265 
1266 void VideoWidget::setZoom(float zoom, bool force)
1267 {
1268     if (!force && m_zoom == zoom) {
1269         return;
1270     }
1271     double zoomRatio = double(zoom / m_zoom);
1272     m_zoom = zoom;
1273     Q_EMIT zoomChanged(zoomRatio);
1274     if (rootObject()) {
1275         rootObject()->setProperty("zoom", m_zoom);
1276         double scalex = rootObject()->property("scalex").toDouble() * zoomRatio;
1277         rootObject()->setProperty("scalex", scalex);
1278         double scaley = rootObject()->property("scaley").toDouble() * zoomRatio;
1279         rootObject()->setProperty("scaley", scaley);
1280     }
1281     resizeGL(width(), height());
1282 }
1283 
1284 void VideoWidget::onFrameDisplayed(const SharedFrame &frame)
1285 {
1286     m_contextSharedAccess.lock();
1287     m_sharedFrame = frame;
1288     m_sendFrame = sendFrameForAnalysis;
1289     m_contextSharedAccess.unlock();
1290     quickWindow()->update();
1291 }
1292 
1293 void VideoWidget::mouseReleaseEvent(QMouseEvent *event)
1294 {
1295     QQuickWidget::mouseReleaseEvent(event);
1296     /*if (m_dragStart.isNull() && m_panStart.isNull() && (rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") &&
1297         !(event->modifiers() & Qt::ControlModifier)) {
1298         event->accept();
1299         qDebug()<<"::::::: MOUSE RELEASED B IGNORED";
1300         return;
1301     }*/
1302     if ((event->modifiers() & Qt::ControlModifier)) {
1303         event->accept();
1304         return;
1305     }
1306     if (!m_dragStart.isNull() && m_panStart.isNull() && ((event->button() & Qt::LeftButton) != 0u) && !event->isAccepted()) {
1307         event->accept();
1308         Q_EMIT monitorPlay();
1309     }
1310     m_dragStart = QPoint();
1311     m_panStart = QPoint();
1312     setCursor(Qt::ArrowCursor);
1313 }
1314 
1315 void VideoWidget::purgeCache()
1316 {
1317     if (m_consumer) {
1318         // m_consumer->set("buffer", 1);
1319         m_consumer->purge();
1320         m_producer->seek(m_proxy->getPosition() + 1);
1321     }
1322 }
1323 
1324 void VideoWidget::mouseDoubleClickEvent(QMouseEvent *event)
1325 {
1326     QQuickWidget::mouseDoubleClickEvent(event);
1327     if (event->isAccepted()) {
1328         return;
1329     }
1330     if ((rootObject() == nullptr) || rootObject()->objectName() != QLatin1String("rooteffectscene")) {
1331         Q_EMIT switchFullScreen();
1332     }
1333     event->accept();
1334 }
1335 
1336 void VideoWidget::setOffsetX(int x, int max)
1337 {
1338     m_offset.setX(x);
1339     if (rootObject()) {
1340         rootObject()->setProperty("offsetx", m_zoom > 1.0f ? x - max / 2.0f + 10 * m_zoom : 0);
1341     }
1342     quickWindow()->update();
1343 }
1344 
1345 void VideoWidget::setOffsetY(int y, int max)
1346 {
1347     m_offset.setY(y);
1348     if (rootObject()) {
1349         rootObject()->setProperty("offsety", m_zoom > 1.0f ? y - max / 2.0f + 10 * m_zoom : 0);
1350     }
1351     quickWindow()->update();
1352 }
1353 
1354 std::shared_ptr<Mlt::Consumer> VideoWidget::consumer()
1355 {
1356     return m_consumer;
1357 }
1358 
1359 Mlt::Producer *VideoWidget::producer()
1360 {
1361     return m_producer.get();
1362 }
1363 
1364 void VideoWidget::resetConsumer(bool fullReset)
1365 {
1366     if (fullReset && m_consumer) {
1367         m_consumer->purge();
1368         m_consumer->stop();
1369         m_consumer.reset();
1370     }
1371     reconfigure();
1372 }
1373 
1374 void VideoWidget::updateTexture(GLuint yName, GLuint uName, GLuint vName)
1375 {
1376     m_texture[0] = yName;
1377     m_texture[1] = uName;
1378     m_texture[2] = vName;
1379 }
1380 
1381 void VideoWidget::on_frame_show(mlt_consumer, VideoWidget *widget, mlt_event_data data)
1382 {
1383     auto frame = Mlt::EventData(data).to_frame();
1384     if (frame.is_valid() && frame.get_int("rendered")) {
1385         int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
1386         if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
1387             QMetaObject::invokeMethod(widget->m_frameRenderer, "showFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
1388         }
1389     }
1390 }
1391 
1392 void VideoWidget::on_gl_nosync_frame_show(mlt_consumer, VideoWidget *widget, mlt_event_data data)
1393 {
1394     auto frame = Mlt::EventData(data).to_frame();
1395     if (frame.get_int("rendered") != 0) {
1396         int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
1397         if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
1398             QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLNoSyncFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
1399         }
1400     }
1401 }
1402 
1403 void VideoWidget::on_gl_frame_show(mlt_consumer, VideoWidget *widget, mlt_event_data data)
1404 {
1405     auto frame = Mlt::EventData(data).to_frame();
1406     if (frame.get_int("rendered") != 0) {
1407         int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
1408         if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
1409             QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
1410         }
1411     }
1412 }
1413 
1414 RenderThread::RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface)
1415     : QThread(nullptr)
1416     , m_function(function)
1417     , m_data(data)
1418     , m_context(nullptr)
1419     , m_surface(surface)
1420 {
1421     if (context) {
1422         m_context = new QOpenGLContext;
1423         m_context->setFormat(context->format());
1424         m_context->setShareContext(context);
1425         m_context->create();
1426         m_context->moveToThread(this);
1427     }
1428 }
1429 
1430 RenderThread::~RenderThread()
1431 {
1432     // would otherwise leak if RenderThread is allocated with a context but not run.
1433     // safe post-run
1434     delete m_context;
1435 }
1436 
1437 // TODO: missing some exception handling?
1438 void RenderThread::run()
1439 {
1440     if (m_context) {
1441         m_context->makeCurrent(m_surface);
1442     }
1443     m_function(m_data);
1444     if (m_context) {
1445         m_context->doneCurrent();
1446         delete m_context;
1447         m_context = nullptr;
1448     }
1449 }
1450 
1451 FrameRenderer::FrameRenderer(QOpenGLContext *shareContext, QSurface *surface, VideoWidget::ClientWaitSync_fp clientWaitSync)
1452     : QThread(nullptr)
1453     , m_semaphore(3)
1454     , m_context(nullptr)
1455     , m_surface(surface)
1456     , m_ClientWaitSync(clientWaitSync)
1457     , m_gl32(nullptr)
1458     , sendAudioForAnalysis(false)
1459 {
1460     Q_ASSERT(shareContext);
1461     m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
1462     m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
1463     // B & C & D
1464     if (KdenliveSettings::gpu_accel() || shareContext->supportsThreadedOpenGL()) {
1465         m_context = new QOpenGLContext;
1466         m_context->setFormat(shareContext->format());
1467         m_context->setShareContext(shareContext);
1468         m_context->create();
1469         m_context->moveToThread(this);
1470     }
1471     setObjectName(QStringLiteral("FrameRenderer"));
1472     moveToThread(this);
1473     start();
1474 }
1475 
1476 FrameRenderer::~FrameRenderer()
1477 {
1478     delete m_context;
1479     delete m_gl32;
1480 }
1481 
1482 void FrameRenderer::showFrame(Mlt::Frame frame)
1483 {
1484     // Save this frame for future use and to keep a reference to the GL Texture.
1485     m_displayFrame = SharedFrame(frame);
1486 
1487     if ((m_context != nullptr) && m_context->isValid()) {
1488         m_context->makeCurrent(m_surface);
1489         // Upload each plane of YUV to a texture.
1490         QOpenGLFunctions *f = m_context->functions();
1491         uploadTextures(m_context, m_displayFrame, m_renderTexture);
1492         f->glBindTexture(GL_TEXTURE_2D, 0);
1493         check_error(f);
1494         f->glFinish();
1495 
1496         for (int i = 0; i < 3; ++i) {
1497             std::swap(m_renderTexture[i], m_displayTexture[i]);
1498         }
1499         Q_EMIT textureReady(m_displayTexture[0], m_displayTexture[1], m_displayTexture[2]);
1500         m_context->doneCurrent();
1501     }
1502     // The frame is now done being modified and can be shared with the rest
1503     // of the application.
1504     Q_EMIT frameDisplayed(m_displayFrame);
1505     m_semaphore.release();
1506 }
1507 
1508 void FrameRenderer::showGLFrame(Mlt::Frame frame)
1509 {
1510     if ((m_context != nullptr) && m_context->isValid()) {
1511         m_context->makeCurrent(m_surface);
1512         pipelineSyncToFrame(frame);
1513 
1514         m_context->functions()->glFinish();
1515         m_context->doneCurrent();
1516 
1517         // Save this frame for future use and to keep a reference to the GL Texture.
1518         m_displayFrame = SharedFrame(frame);
1519     }
1520     // The frame is now done being modified and can be shared with the rest
1521     // of the application.
1522     Q_EMIT frameDisplayed(m_displayFrame);
1523     m_semaphore.release();
1524 }
1525 
1526 void FrameRenderer::showGLNoSyncFrame(Mlt::Frame frame)
1527 {
1528     if ((m_context != nullptr) && m_context->isValid()) {
1529 
1530         frame.set("movit.convert.use_texture", 1);
1531         m_context->makeCurrent(m_surface);
1532         m_context->functions()->glFinish();
1533 
1534         m_context->doneCurrent();
1535 
1536         // Save this frame for future use and to keep a reference to the GL Texture.
1537         m_displayFrame = SharedFrame(frame);
1538     }
1539     // The frame is now done being modified and can be shared with the rest
1540     // of the application.
1541     Q_EMIT frameDisplayed(m_displayFrame);
1542     m_semaphore.release();
1543 }
1544 
1545 void FrameRenderer::cleanup()
1546 {
1547     if ((m_renderTexture[0] != 0u) && (m_renderTexture[1] != 0u) && (m_renderTexture[2] != 0u)) {
1548         m_context->makeCurrent(m_surface);
1549         m_context->functions()->glDeleteTextures(3, m_renderTexture);
1550         if ((m_displayTexture[0] != 0u) && (m_displayTexture[1] != 0u) && (m_displayTexture[2] != 0u)) {
1551             m_context->functions()->glDeleteTextures(3, m_displayTexture);
1552         }
1553         m_context->doneCurrent();
1554         m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
1555         m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
1556     }
1557 }
1558 
1559 // D
1560 void FrameRenderer::pipelineSyncToFrame(Mlt::Frame &frame)
1561 {
1562     auto sync = GLsync(frame.get_data("movit.convert.fence"));
1563     if (!sync) return;
1564 
1565 #ifdef Q_OS_WIN
1566     // On Windows, use QOpenGLFunctions_3_2_Core instead of getProcAddress.
1567     // TODO: move to initialization of m_ClientWaitSync
1568     if (!m_gl32) {
1569 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1570         // TODO: Qt6
1571         m_gl32 = m_context->versionFunctions<QOpenGLFunctions_3_2_Core>();
1572 #endif
1573         if (m_gl32) {
1574             m_gl32->initializeOpenGLFunctions();
1575         }
1576     }
1577     if (m_gl32) {
1578         m_gl32->glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
1579         check_error(m_context->functions());
1580     }
1581 #else
1582     if (m_ClientWaitSync) {
1583         m_ClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
1584         check_error(m_context->functions());
1585     }
1586 #endif // Q_OS_WIN
1587 }
1588 
1589 void VideoWidget::refreshSceneLayout()
1590 {
1591     if (!rootObject()) {
1592         return;
1593     }
1594     QSize s = pCore->getCurrentFrameSize();
1595     Q_EMIT m_proxy->profileChanged();
1596     rootObject()->setProperty("scalex", double(m_rect.width() * m_zoom) / s.width());
1597     rootObject()->setProperty("scaley", double(m_rect.height() * m_zoom) / s.height());
1598 }
1599 
1600 bool VideoWidget::switchPlay(bool play, double speed)
1601 {
1602     if (!m_producer || !m_consumer) {
1603         return false;
1604     }
1605     if (m_isZoneMode || m_isLoopMode) {
1606         resetZoneMode();
1607     }
1608     if (play) {
1609         if (m_consumer->position() >= m_maxProducerPosition && speed > 0) {
1610             // We are at the end of the clip / timeline
1611             if (m_id == Kdenlive::ClipMonitor || (m_id == Kdenlive::ProjectMonitor && KdenliveSettings::jumptostart())) {
1612                 m_producer->seek(0);
1613             } else {
1614                 return false;
1615             }
1616         }
1617         qDebug() << "pos: " << m_consumer->position() << "out: " << m_producer->get_playtime() - 1;
1618         double current_speed = m_producer->get_speed();
1619         m_producer->set_speed(speed);
1620         m_proxy->setSpeed(speed);
1621         if (qFuzzyCompare(speed, 1.0) || speed < -6. || speed > 6.) {
1622             m_consumer->set("scrub_audio", 0);
1623         } else if (KdenliveSettings::audio_scrub()) {
1624             m_consumer->set("scrub_audio", 1);
1625         }
1626         if (qFuzzyIsNull(current_speed)) {
1627             m_consumer->start();
1628             m_consumer->set("refresh", 1);
1629             m_consumer->set("volume", KdenliveSettings::volume() / 100.);
1630         } else {
1631             // Speed change, purge to reduce latency
1632             m_consumer->purge();
1633             m_producer->seek(m_consumer->position() + (speed > 1. ? 1 : 0));
1634         }
1635     } else {
1636         Q_EMIT paused();
1637         m_producer->set_speed(0);
1638         m_consumer->set("volume", 0);
1639         m_proxy->setSpeed(0);
1640         m_producer->seek(m_consumer->position() + 1);
1641         m_consumer->purge();
1642         m_consumer->start();
1643         m_consumer->set("scrub_audio", 0);
1644     }
1645     return true;
1646 }
1647 
1648 bool VideoWidget::playZone(bool loop)
1649 {
1650     if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
1651         pCore->displayMessage(i18n("Select a zone to play"), ErrorMessage, 500);
1652         return false;
1653     }
1654     double current_speed = m_producer->get_speed();
1655     m_producer->set_speed(0);
1656     m_proxy->setSpeed(0);
1657     m_loopOut = m_proxy->zoneOut();
1658     m_loopIn = m_proxy->zoneIn();
1659     if (qFuzzyIsNull(current_speed)) {
1660         m_producer->seek(m_proxy->zoneIn());
1661         m_consumer->start();
1662         m_consumer->set("scrub_audio", 0);
1663         m_consumer->set("refresh", 1);
1664         m_consumer->set("volume", KdenliveSettings::volume() / 100.);
1665         m_producer->set_speed(1.0);
1666     } else {
1667         // Speed change, purge to reduce latency
1668         m_consumer->set("refresh", 0);
1669         m_producer->seek(m_loopIn);
1670         m_consumer->purge();
1671         m_producer->set_speed(1.0);
1672         m_consumer->set("refresh", 1);
1673     }
1674     m_isZoneMode = true;
1675     m_isLoopMode = loop;
1676     return true;
1677 }
1678 
1679 bool VideoWidget::restartConsumer()
1680 {
1681     int result = 0;
1682     if (m_consumer->is_stopped()) {
1683         // When restarting the consumer, we need to restore the preview scaling
1684         int cWidth = m_consumer->get_int("width");
1685         int cHeigth = m_consumer->get_int("height");
1686         result = m_consumer->start();
1687         if (cWidth > 0) {
1688             m_consumer->set("width", cWidth);
1689             m_consumer->set("height", cHeigth);
1690         }
1691     }
1692     return result != -1;
1693 }
1694 
1695 bool VideoWidget::loopClip(QPoint inOut)
1696 {
1697     if (!m_producer || inOut.y() <= inOut.x()) {
1698         pCore->displayMessage(i18n("Select a clip to play"), ErrorMessage, 500);
1699         return false;
1700     }
1701     m_loopIn = inOut.x();
1702     double current_speed = m_producer->get_speed();
1703     m_producer->set_speed(0);
1704     m_proxy->setSpeed(0);
1705     m_loopOut = inOut.y();
1706     if (qFuzzyIsNull(current_speed)) {
1707         m_producer->seek(m_loopIn);
1708         m_consumer->start();
1709         m_consumer->set("scrub_audio", 0);
1710         m_consumer->set("refresh", 1);
1711         m_consumer->set("volume", KdenliveSettings::volume() / 100.);
1712         m_producer->set_speed(1.0);
1713     } else {
1714         // Speed change, purge to reduce latency
1715         m_consumer->set("refresh", 0);
1716         m_consumer->purge();
1717         m_producer->seek(m_loopIn);
1718         m_producer->set_speed(1.0);
1719         m_consumer->set("refresh", 1);
1720     }
1721     m_isZoneMode = false;
1722     m_isLoopMode = true;
1723     return true;
1724 }
1725 
1726 void VideoWidget::resetZoneMode()
1727 {
1728     if (!m_isZoneMode && !m_isLoopMode) {
1729         return;
1730     }
1731     m_loopIn = 0;
1732     m_loopOut = 0;
1733     m_isZoneMode = false;
1734     m_isLoopMode = false;
1735 }
1736 
1737 MonitorProxy *VideoWidget::getControllerProxy()
1738 {
1739     return m_proxy;
1740 }
1741 
1742 int VideoWidget::getCurrentPos() const
1743 {
1744     return m_proxy->getPosition();
1745 }
1746 
1747 void VideoWidget::setRulerInfo(int duration, const std::shared_ptr<MarkerSortModel> &model)
1748 {
1749     m_maxProducerPosition = duration;
1750     rootObject()->setProperty("duration", duration);
1751     if (model != nullptr) {
1752         // we are resetting marker/snap model, reset zone
1753         rootContext()->setContextProperty("markersModel", model.get());
1754     }
1755 }
1756 
1757 void VideoWidget::switchRecordState(bool on)
1758 {
1759     if (on) {
1760         if (m_maxProducerPosition == 0x7fffffff) {
1761             // We are already in rec mode
1762             return;
1763         }
1764         m_bckpMax = m_maxProducerPosition;
1765         m_maxProducerPosition = 0x7fffffff;
1766     } else {
1767         m_maxProducerPosition = m_bckpMax;
1768     }
1769 }
1770 
1771 void VideoWidget::startConsumer()
1772 {
1773     if (m_consumer == nullptr) {
1774         return;
1775     }
1776     if (!restartConsumer()) {
1777         // ARGH CONSUMER BROKEN!!!!
1778         KMessageBox::error(
1779             qApp->activeWindow(),
1780             i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
1781         if (m_displayEvent) {
1782             delete m_displayEvent;
1783         }
1784         m_displayEvent = nullptr;
1785         m_consumer.reset();
1786         return;
1787     }
1788     m_consumer->set("refresh", 1);
1789 }
1790 
1791 void VideoWidget::stop()
1792 {
1793     m_refreshTimer.stop();
1794     // why this lock?
1795     QMutexLocker locker(&m_mltMutex);
1796     if (m_producer) {
1797         if (m_isZoneMode || m_isLoopMode) {
1798             resetZoneMode();
1799         }
1800         m_producer->set_speed(0.0);
1801         m_proxy->setSpeed(0);
1802     }
1803     if (m_consumer) {
1804         m_consumer->purge();
1805         if (!m_consumer->is_stopped()) {
1806             m_consumer->stop();
1807         }
1808     }
1809 }
1810 
1811 double VideoWidget::playSpeed() const
1812 {
1813     if (m_producer) {
1814         return m_producer->get_speed();
1815     }
1816     return 0.0;
1817 }
1818 
1819 void VideoWidget::restart()
1820 {
1821     // why this lock?
1822     if (m_consumer) {
1823         // Make sure to delete and rebuild consumer to match profile
1824         m_consumer->purge();
1825         m_consumer->stop();
1826         reconfigure();
1827     }
1828 }
1829 
1830 int VideoWidget::volume() const
1831 {
1832     if ((!m_consumer) || (!m_producer)) {
1833         return -1;
1834     }
1835     if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
1836         return (int(100 * m_consumer->get_double("0.volume")));
1837     }
1838     return (int(100 * m_consumer->get_double("volume")));
1839 }
1840 
1841 void VideoWidget::setVolume(double volume)
1842 {
1843     if (m_consumer) {
1844         if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
1845             m_consumer->set("0.volume", volume);
1846         } else {
1847             m_consumer->set("volume", volume);
1848         }
1849     }
1850 }
1851 
1852 int VideoWidget::duration() const
1853 {
1854     if (!m_producer) {
1855         return 0;
1856     }
1857     return m_producer->get_playtime();
1858 }
1859 
1860 void VideoWidget::setConsumerProperty(const QString &name, const QString &value)
1861 {
1862     QMutexLocker locker(&m_mltMutex);
1863     if (m_consumer) {
1864         m_consumer->set(name.toUtf8().constData(), value.toUtf8().constData());
1865         if (m_consumer->start() == -1) {
1866             qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
1867         }
1868     }
1869 }
1870 
1871 bool VideoWidget::updateScaling()
1872 {
1873     int previewHeight = pCore->getCurrentFrameSize().height();
1874     switch (KdenliveSettings::previewScaling()) {
1875     case 2:
1876         previewHeight = qMin(previewHeight, 720);
1877         break;
1878     case 4:
1879         previewHeight = qMin(previewHeight, 540);
1880         break;
1881     case 8:
1882         previewHeight = qMin(previewHeight, 360);
1883         break;
1884     case 16:
1885         previewHeight = qMin(previewHeight, 270);
1886         break;
1887     default:
1888         break;
1889     }
1890     int pWidth = int(previewHeight * pCore->getCurrentDar() / pCore->getCurrentSar());
1891     if (pWidth % 2 > 0) {
1892         pWidth++;
1893     }
1894     QSize profileSize(pWidth, previewHeight);
1895     if (profileSize == m_profileSize) {
1896         return false;
1897     }
1898     m_profileSize = profileSize;
1899     pCore->getMonitorProfile().set_width(m_profileSize.width());
1900     pCore->getMonitorProfile().set_height(m_profileSize.height());
1901     if (m_consumer) {
1902         m_consumer->set("width", m_profileSize.width());
1903         m_consumer->set("height", m_profileSize.height());
1904         resizeGL(width(), height());
1905     }
1906     return true;
1907 }
1908 
1909 void VideoWidget::switchRuler(bool show)
1910 {
1911     m_rulerHeight = show ? int(QFontInfo(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)).pixelSize() * 1.5) : 0;
1912     m_displayRulerHeight = m_rulerHeight;
1913     resizeGL(width(), height());
1914     Q_EMIT m_proxy->rulerHeightChanged();
1915 }
1916 
1917 const QStringList VideoWidget::getGPUInfo()
1918 {
1919     if (!m_isInitialized) {
1920         return {};
1921     }
1922     return {QString::fromUtf8((const char *)glGetString(GL_VENDOR)), QString::fromUtf8((const char *)glGetString(GL_RENDERER))};
1923 }