File indexing completed on 2024-12-22 04:12:46

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006-2013 Boudewijn Rempt <boud@valdyas.org>
0003  * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #define GL_GLEXT_PROTOTYPES
0009 
0010 #include "opengl/kis_opengl_canvas2.h"
0011 #include "opengl/KisOpenGLCanvasRenderer.h"
0012 #include "opengl/KisOpenGLSync.h"
0013 #include "opengl/kis_opengl_canvas_debugger.h"
0014 
0015 #include "canvas/kis_canvas2.h"
0016 #include <kis_canvas_resource_provider.h>
0017 #include "kis_config.h"
0018 #include "kis_config_notifier.h"
0019 #include "kis_debug.h"
0020 #include <KisViewManager.h>
0021 #include "KisRepaintDebugger.h"
0022 
0023 #include <QPointer>
0024 #include "KisOpenGLModeProber.h"
0025 
0026 static bool OPENGL_SUCCESS = false;
0027 
0028 class KisOpenGLCanvas2::CanvasBridge
0029     : public KisOpenGLCanvasRenderer::CanvasBridge
0030 {
0031     friend class KisOpenGLCanvas2;
0032     explicit CanvasBridge(KisOpenGLCanvas2 *canvas)
0033         : m_canvas(canvas)
0034     {}
0035     ~CanvasBridge() override = default;
0036     Q_DISABLE_COPY(CanvasBridge)
0037     KisOpenGLCanvas2 *m_canvas;
0038 protected:
0039     KisCanvas2 *canvas() const override {
0040         return m_canvas->canvas();
0041     }
0042     QOpenGLContext *openglContext() const override {
0043         return m_canvas->context();
0044     }
0045     qreal devicePixelRatioF() const override {
0046         return m_canvas->devicePixelRatioF();
0047     }
0048     KisCoordinatesConverter *coordinatesConverter() const override {
0049         return m_canvas->coordinatesConverter();
0050     }
0051     QColor borderColor() const override {
0052         return m_canvas->borderColor();
0053     }
0054 };
0055 
0056 struct KisOpenGLCanvas2::Private
0057 {
0058 public:
0059     ~Private() {
0060         delete renderer;
0061     }
0062 
0063     boost::optional<QRect> updateRect;
0064     QRect canvasImageDirtyRect;
0065     KisOpenGLCanvasRenderer *renderer;
0066     QScopedPointer<KisOpenGLSync> glSyncObject;
0067     KisRepaintDebugger repaintDbg;
0068 };
0069 
0070 KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas,
0071                                    KisCoordinatesConverter *coordinatesConverter,
0072                                    QWidget *parent,
0073                                    KisImageWSP image,
0074                                    KisDisplayColorConverter *colorConverter)
0075     : QOpenGLWidget(parent)
0076     , KisCanvasWidgetBase(canvas, coordinatesConverter)
0077     , d(new Private())
0078 {
0079     KisConfig cfg(false);
0080     cfg.setCanvasState("OPENGL_STARTED");
0081 
0082     d->renderer = new KisOpenGLCanvasRenderer(new CanvasBridge(this), image, colorConverter);
0083 
0084     connect(d->renderer->openGLImageTextures().data(),
0085             SIGNAL(sigShowFloatingMessage(QString, int, bool)),
0086             SLOT(slotShowFloatingMessage(QString, int, bool)));
0087 
0088     setAcceptDrops(true);
0089     setAutoFillBackground(false);
0090 
0091     setFocusPolicy(Qt::StrongFocus);
0092     setAttribute(Qt::WA_NoSystemBackground, true);
0093 #ifdef Q_OS_MACOS
0094     setAttribute(Qt::WA_AcceptTouchEvents, false);
0095 #else
0096     setAttribute(Qt::WA_AcceptTouchEvents, true);
0097 #endif
0098     setAttribute(Qt::WA_InputMethodEnabled, true);
0099     setAttribute(Qt::WA_DontCreateNativeAncestors, true);
0100     setUpdateBehavior(PartialUpdate);
0101 
0102 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0103     // we should make sure the texture doesn't have alpha channel,
0104     // otherwise blending will not work correctly.
0105     if (KisOpenGLModeProber::instance()->useHDRMode()) {
0106         setTextureFormat(GL_RGBA16F);
0107     } else {
0108         /**
0109          * When in pure OpenGL mode, the canvas surface will have alpha
0110          * channel. Therefore, if our canvas blending algorithm produces
0111          * semi-transparent pixels (and it does), then Krita window itself
0112          * will become transparent. Which is not good.
0113          *
0114          * In Angle mode, GL_RGB8 is not available (and the transparence effect
0115          * doesn't exist at all).
0116          */
0117         if (!KisOpenGL::hasOpenGLES()) {
0118             setTextureFormat(GL_RGB8);
0119         }
0120     }
0121 #endif
0122 
0123     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0124     connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged()));
0125 
0126     connect(canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(slotUpdateCursorColor()));
0127     connect(canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigPaintOpPresetChanged(KisPaintOpPresetSP)), SLOT(slotUpdateCursorColor()));
0128 
0129     slotConfigChanged();
0130     slotPixelGridModeChanged();
0131     cfg.writeEntry("canvasState", "OPENGL_SUCCESS");
0132 }
0133 
0134 KisOpenGLCanvas2::~KisOpenGLCanvas2()
0135 {
0136     /**
0137      * Since we delete openGL resources, we should make sure the
0138      * context is initialized properly before they are deleted.
0139      * Otherwise resources from some other (current) context may be
0140      * deleted due to resource id aliasing.
0141      *
0142      * The main symptom of resources being deleted from wrong context,
0143      * the canvas being locked/backened-out after some other document
0144      * is closed.
0145      */
0146 
0147     makeCurrent();
0148 
0149     delete d;
0150 
0151     doneCurrent();
0152 }
0153 
0154 void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
0155 {
0156     d->renderer->setDisplayFilter(displayFilter);
0157 }
0158 
0159 void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs)
0160 {
0161     d->renderer->notifyImageColorSpaceChanged(cs);
0162 }
0163 
0164 void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value)
0165 {
0166     d->renderer->setWrapAroundViewingMode(value);
0167     update();
0168 }
0169 
0170 bool KisOpenGLCanvas2::wrapAroundViewingMode() const
0171 {
0172     return d->renderer->wrapAroundViewingMode();
0173 }
0174 
0175 void KisOpenGLCanvas2::setWrapAroundViewingModeAxis(WrapAroundAxis value)
0176 {
0177     d->renderer->setWrapAroundViewingModeAxis(value);
0178     update();
0179 }
0180 
0181 WrapAroundAxis KisOpenGLCanvas2::wrapAroundViewingModeAxis() const
0182 {
0183     return d->renderer->wrapAroundViewingModeAxis();
0184 }
0185 
0186 void KisOpenGLCanvas2::initializeGL()
0187 {
0188     d->renderer->initializeGL();
0189     KisOpenGLSync::init(context());
0190 }
0191 
0192 void KisOpenGLCanvas2::resizeGL(int width, int height)
0193 {
0194     d->renderer->resizeGL(width, height);
0195     d->canvasImageDirtyRect = QRect(0, 0, width, height);
0196 }
0197 
0198 void KisOpenGLCanvas2::paintGL()
0199 {
0200     const QRect updateRect = d->updateRect ? *d->updateRect : QRect();
0201 
0202     if (!OPENGL_SUCCESS) {
0203         KisConfig cfg(false);
0204         cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED");
0205     }
0206 
0207     KisOpenglCanvasDebugger::instance()->notifyPaintRequested();
0208     QRect canvasImageDirtyRect = d->canvasImageDirtyRect & rect();
0209     d->canvasImageDirtyRect = QRect();
0210     d->renderer->paintCanvasOnly(canvasImageDirtyRect, updateRect);
0211     {
0212         QPainter gc(this);
0213         if (!updateRect.isEmpty()) {
0214             gc.setClipRect(updateRect);
0215         }
0216 
0217         QRect decorationsBoundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect();
0218 
0219         if (!updateRect.isEmpty()) {
0220             decorationsBoundingRect &= updateRect;
0221         }
0222 
0223         drawDecorations(gc, decorationsBoundingRect);
0224     }
0225 
0226     d->repaintDbg.paint(this, updateRect.isEmpty() ? rect() : updateRect);
0227 
0228     // We create the glFenceSync object here instead of in KisOpenGLRenderer,
0229     // because the glFenceSync object should be created after all render
0230     // commands in a frame, not just the OpenGL canvas itself. Putting it
0231     // outside of KisOpenGLRenderer allows the canvas widget to do extra
0232     // rendering, which a QtQuick2-based canvas will need.
0233     d->glSyncObject.reset(new KisOpenGLSync());
0234 
0235     if (!OPENGL_SUCCESS) {
0236         KisConfig cfg(false);
0237         cfg.writeEntry("canvasState", "OPENGL_SUCCESS");
0238         OPENGL_SUCCESS = true;
0239     }
0240 }
0241 
0242 void KisOpenGLCanvas2::paintEvent(QPaintEvent *e)
0243 {
0244     KIS_SAFE_ASSERT_RECOVER_RETURN(!d->updateRect);
0245 
0246     if (qFuzzyCompare(devicePixelRatioF(), qRound(devicePixelRatioF()))) {
0247         /**
0248          * Enable partial updates **only** for the case when we have
0249          * integer scaling. There is a bug in Qt that causes artifacts
0250          * otherwise:
0251          *
0252          * See https://bugs.kde.org/show_bug.cgi?id=441216
0253          */
0254         d->updateRect = e->rect();
0255     } else {
0256         d->updateRect = this->rect();
0257     }
0258 
0259     QOpenGLWidget::paintEvent(e);
0260     d->updateRect = boost::none;
0261 }
0262 
0263 void KisOpenGLCanvas2::paintToolOutline(const KisOptimizedBrushOutline &path, int thickness)
0264 {
0265     /**
0266      * paintToolOutline() is called from drawDecorations(), which has clipping
0267      * set only for QPainter-based painting; here we paint in native mode, so we
0268      * should care about clipping manually
0269      *
0270      * `d->updateRect` might be empty in case the fractional DPI workaround
0271      * is active.
0272      */
0273     const QRect updateRect = d->updateRect ? *d->updateRect : QRect();
0274 
0275     d->renderer->paintToolOutline(path, updateRect, thickness);
0276 }
0277 
0278 bool KisOpenGLCanvas2::isBusy() const
0279 {
0280     const bool isBusyStatus = d->glSyncObject && !d->glSyncObject->isSignaled();
0281     KisOpenglCanvasDebugger::instance()->notifySyncStatus(isBusyStatus);
0282     return isBusyStatus;
0283 }
0284 
0285 void KisOpenGLCanvas2::setLodResetInProgress(bool value)
0286 {
0287     d->renderer->setLodResetInProgress(value);
0288 }
0289 
0290 void KisOpenGLCanvas2::slotConfigChanged()
0291 {
0292     d->renderer->updateConfig();
0293 
0294     notifyConfigChanged();
0295 }
0296 
0297 void KisOpenGLCanvas2::slotPixelGridModeChanged()
0298 {
0299     d->renderer->updatePixelGridMode();
0300 
0301     update();
0302 }
0303 
0304 void KisOpenGLCanvas2::slotUpdateCursorColor()
0305 {
0306     d->renderer->updateCursorColor();
0307 }
0308 
0309 void KisOpenGLCanvas2::slotShowFloatingMessage(const QString &message, int timeout, bool priority)
0310 {
0311     canvas()->imageView()->showFloatingMessage(message, QIcon(), timeout, priority ? KisFloatingMessage::High : KisFloatingMessage::Medium);
0312 }
0313 
0314 QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const
0315 {
0316     return processInputMethodQuery(query);
0317 }
0318 
0319 void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event)
0320 {
0321     processInputMethodEvent(event);
0322 }
0323 
0324 void KisOpenGLCanvas2::focusInEvent(QFocusEvent *event)
0325 {
0326     processFocusInEvent(event);
0327 }
0328 
0329 void KisOpenGLCanvas2::focusOutEvent(QFocusEvent *event)
0330 {
0331     processFocusOutEvent(event);
0332 }
0333 
0334 void KisOpenGLCanvas2::hideEvent(QHideEvent *e)
0335 {
0336     QOpenGLWidget::hideEvent(e);
0337     notifyDecorationsWindowMinimized(true);
0338 }
0339 
0340 void KisOpenGLCanvas2::showEvent(QShowEvent *e)
0341 {
0342     QOpenGLWidget::showEvent(e);
0343     notifyDecorationsWindowMinimized(false);
0344 }
0345 
0346 void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter)
0347 {
0348     d->renderer->setDisplayColorConverter(colorConverter);
0349 }
0350 
0351 void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags)
0352 {
0353     d->renderer->channelSelectionChanged(channelFlags);
0354 }
0355 
0356 
0357 void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h)
0358 {
0359     d->renderer->finishResizingImage(w, h);
0360 }
0361 
0362 KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc)
0363 {
0364     return d->renderer->startUpdateCanvasProjection(rc);
0365 }
0366 
0367 
0368 QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info)
0369 {
0370     return d->renderer->updateCanvasProjection(info);
0371 }
0372 
0373 QVector<QRect> KisOpenGLCanvas2::updateCanvasProjection(const QVector<KisUpdateInfoSP> &infoObjects)
0374 {
0375 #if defined(Q_OS_MACOS) || defined(Q_OS_ANDROID)
0376     /**
0377      * On OSX openGL different (shared) contexts have different execution queues.
0378      * It means that the textures uploading and their painting can be easily reordered.
0379      * To overcome the issue, we should ensure that the textures are uploaded in the
0380      * same openGL context as the painting is done.
0381      */
0382 
0383     QOpenGLContext *oldContext = QOpenGLContext::currentContext();
0384     QSurface *oldSurface = oldContext ? oldContext->surface() : 0;
0385 
0386     this->makeCurrent();
0387 #endif
0388 
0389     QVector<QRect> result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects);
0390 
0391 #if defined(Q_OS_MACOS) || defined(Q_OS_ANDROID)
0392     if (oldContext) {
0393         oldContext->makeCurrent(oldSurface);
0394     } else {
0395         this->doneCurrent();
0396     }
0397 #endif
0398 
0399     return result;
0400 }
0401 
0402 void KisOpenGLCanvas2::updateCanvasImage(const QRect &imageUpdateRect)
0403 {
0404     d->canvasImageDirtyRect |= imageUpdateRect;
0405     update(imageUpdateRect);
0406 }
0407 
0408 void KisOpenGLCanvas2::updateCanvasDecorations(const QRect &decoUpdateRect)
0409 {
0410     update(decoUpdateRect);
0411 }
0412 bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next)
0413 {
0414     return focusNextPrevChild(next);
0415 }
0416 
0417 KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const
0418 {
0419     return d->renderer->openGLImageTextures();
0420 }