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 }