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

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/KisOpenGLCanvasRenderer.h"
0011 
0012 #include "kis_algebra_2d.h"
0013 #include "opengl/kis_opengl_shader_loader.h"
0014 #include "canvas/kis_canvas2.h"
0015 #include "canvas/kis_coordinates_converter.h"
0016 #include "canvas/kis_display_filter.h"
0017 #include "canvas/kis_display_color_converter.h"
0018 #include "canvas/kis_canvas_widget_base.h"
0019 #include "KisOpenGLModeProber.h"
0020 #include "kis_canvas_resource_provider.h"
0021 #include "kis_config.h"
0022 #include "kis_debug.h"
0023 
0024 #include <QPainter>
0025 #include <QPainterPath>
0026 #include <QOpenGLPaintDevice>
0027 #include <QPointF>
0028 #include <QPointer>
0029 #include <QMatrix>
0030 #include <QTransform>
0031 #include <QThread>
0032 #include <QFile>
0033 #include <QOpenGLShaderProgram>
0034 #include <QOpenGLVertexArrayObject>
0035 #include <QOpenGLBuffer>
0036 #include <QOpenGLFramebufferObject>
0037 #include <QOpenGLFramebufferObjectFormat>
0038 #include <QMessageBox>
0039 #include <KoCompositeOpRegistry.h>
0040 #include <KoColorModelStandardIds.h>
0041 #include "KisOpenGLBufferCircularStorage.h"
0042 #include "kis_painting_tweaks.h"
0043 #include <KisOptimizedBrushOutline.h>
0044 
0045 #include <config-ocio.h>
0046 
0047 #define NEAR_VAL -1000.0
0048 #define FAR_VAL 1000.0
0049 
0050 #ifndef GL_CLAMP_TO_EDGE
0051 #define GL_CLAMP_TO_EDGE 0x812F
0052 #endif
0053 
0054 #define PROGRAM_VERTEX_ATTRIBUTE 0
0055 #define PROGRAM_TEXCOORD_ATTRIBUTE 1
0056 
0057 // These buffers are used only for painting checkers,
0058 // so we can keep the number really low
0059 static constexpr int NumberOfBuffers = 2;
0060 
0061 struct KisOpenGLCanvasRenderer::Private
0062 {
0063 public:
0064     ~Private() {
0065         delete displayShader;
0066         delete checkerShader;
0067         delete solidColorShader;
0068 
0069         delete canvasBridge;
0070     }
0071 
0072     bool canvasInitialized{false};
0073 
0074     KisOpenGLImageTexturesSP openGLImageTextures;
0075 
0076     KisOpenGLShaderLoader shaderLoader;
0077     KisShaderProgram *displayShader{0};
0078     KisShaderProgram *checkerShader{0};
0079     KisShaderProgram *solidColorShader{0};
0080 
0081     QScopedPointer<QOpenGLFramebufferObject> canvasFBO;
0082 
0083     bool displayShaderCompiledWithDisplayFilterSupport{false};
0084 
0085     GLfloat checkSizeScale;
0086     bool scrollCheckers;
0087 
0088     QSharedPointer<KisDisplayFilter> displayFilter;
0089     KisOpenGL::FilterMode filterMode;
0090     bool proofingConfigIsUpdated=false;
0091 
0092     bool wrapAroundMode{false};
0093     WrapAroundAxis wrapAroundModeAxis{WRAPAROUND_BOTH};
0094 
0095     // Stores a quad for drawing the canvas
0096     QOpenGLVertexArrayObject quadVAO;
0097 
0098     KisOpenGLBufferCircularStorage checkersVertexBuffer;
0099     KisOpenGLBufferCircularStorage checkersTextureVertexBuffer;
0100 
0101     // Stores data for drawing tool outlines
0102     QOpenGLVertexArrayObject outlineVAO;
0103     QOpenGLBuffer lineVertexBuffer;
0104 
0105     QVector3D vertices[6];
0106     QVector2D texCoords[6];
0107 
0108     qreal pixelGridDrawingThreshold;
0109     bool pixelGridEnabled;
0110     QColor gridColor;
0111     QColor cursorColor;
0112 
0113     bool lodSwitchInProgress = false;
0114 
0115     CanvasBridge *canvasBridge;
0116     QSizeF pixelAlignedWidgetSize;
0117     QSize viewportDevicePixelSize;
0118 
0119     int xToColWithWrapCompensation(int x, const QRect &imageRect) {
0120         int firstImageColumn = openGLImageTextures->xToCol(imageRect.left());
0121         int lastImageColumn = openGLImageTextures->xToCol(imageRect.right());
0122 
0123         int colsPerImage = lastImageColumn - firstImageColumn + 1;
0124         int numWraps = floor(qreal(x) / imageRect.width());
0125         int remainder = x - imageRect.width() * numWraps;
0126 
0127         return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder);
0128     }
0129 
0130     int yToRowWithWrapCompensation(int y, const QRect &imageRect) {
0131         int firstImageRow = openGLImageTextures->yToRow(imageRect.top());
0132         int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom());
0133 
0134         int rowsPerImage = lastImageRow - firstImageRow + 1;
0135         int numWraps = floor(qreal(y) / imageRect.height());
0136         int remainder = y - imageRect.height() * numWraps;
0137 
0138         return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder);
0139     }
0140 
0141 };
0142 
0143 KisOpenGLCanvasRenderer::KisOpenGLCanvasRenderer(CanvasBridge *canvasBridge,
0144                                                  KisImageWSP image,
0145                                                  KisDisplayColorConverter *colorConverter)
0146     : d(new Private())
0147 {
0148     d->canvasBridge = canvasBridge;
0149 
0150     d->openGLImageTextures =
0151             KisOpenGLImageTextures::getImageTextures(image,
0152                                                      colorConverter->openGLCanvasSurfaceProfile(),
0153                                                      colorConverter->renderingIntent(),
0154                                                      colorConverter->conversionFlags());
0155 
0156 
0157     setDisplayFilterImpl(colorConverter->displayFilter(), true);
0158 }
0159 
0160 KisOpenGLCanvasRenderer::~KisOpenGLCanvasRenderer()
0161 {
0162     delete d;
0163 }
0164 
0165 KisCanvas2 *KisOpenGLCanvasRenderer::canvas() const
0166 {
0167     return d->canvasBridge->canvas();
0168 }
0169 
0170 QOpenGLContext *KisOpenGLCanvasRenderer::context() const
0171 {
0172     return d->canvasBridge->openglContext();
0173 }
0174 
0175 qreal KisOpenGLCanvasRenderer::devicePixelRatioF() const
0176 {
0177     return d->canvasBridge->devicePixelRatioF();
0178 }
0179 
0180 KisCoordinatesConverter *KisOpenGLCanvasRenderer::coordinatesConverter() const
0181 {
0182     return d->canvasBridge->coordinatesConverter();
0183 }
0184 
0185 QColor KisOpenGLCanvasRenderer::borderColor() const
0186 {
0187     return d->canvasBridge->borderColor();
0188 }
0189 
0190 void KisOpenGLCanvasRenderer::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
0191 {
0192     setDisplayFilterImpl(displayFilter, false);
0193 }
0194 
0195 void KisOpenGLCanvasRenderer::setDisplayFilterImpl(QSharedPointer<KisDisplayFilter> displayFilter, bool initializing)
0196 {
0197     bool needsInternalColorManagement =
0198             !displayFilter || displayFilter->useInternalColorManagement();
0199 
0200     bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement);
0201 
0202     d->displayFilter = displayFilter;
0203 
0204     if (!initializing && needsFullRefresh) {
0205         canvas()->startUpdateInPatches(canvas()->image()->bounds());
0206     }
0207     else if (!initializing)  {
0208         canvas()->updateCanvas();
0209     }
0210 }
0211 
0212 void KisOpenGLCanvasRenderer::notifyImageColorSpaceChanged(const KoColorSpace *cs)
0213 {
0214     // FIXME: on color space change the data is refetched multiple
0215     //        times by different actors!
0216 
0217     if (d->openGLImageTextures->setImageColorSpace(cs)) {
0218         canvas()->startUpdateInPatches(canvas()->image()->bounds());
0219     }
0220 }
0221 
0222 void KisOpenGLCanvasRenderer::setWrapAroundViewingMode(bool value)
0223 {
0224     d->wrapAroundMode = value;
0225 }
0226 
0227 bool KisOpenGLCanvasRenderer::wrapAroundViewingMode() const
0228 {
0229     return d->wrapAroundMode;
0230 }
0231 
0232 void KisOpenGLCanvasRenderer::setWrapAroundViewingModeAxis(WrapAroundAxis value)
0233 {
0234     d->wrapAroundModeAxis = value;
0235 }
0236 
0237 WrapAroundAxis KisOpenGLCanvasRenderer::wrapAroundViewingModeAxis() const
0238 {
0239     return d->wrapAroundModeAxis;
0240 }
0241 
0242 void KisOpenGLCanvasRenderer::initializeGL()
0243 {
0244     KisOpenGL::initializeContext(context());
0245     initializeOpenGLFunctions();
0246 
0247     KisConfig cfg(true);
0248     d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
0249     d->openGLImageTextures->initGL(context()->functions());
0250     d->openGLImageTextures->generateCheckerTexture(KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()));
0251 
0252     initializeShaders();
0253 
0254     // If we support OpenGL 3.0, then prepare our VAOs and VBOs for drawing
0255     if (KisOpenGL::supportsVAO()) {
0256         d->quadVAO.create();
0257         d->quadVAO.bind();
0258 
0259         glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
0260         glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE);
0261 
0262         d->checkersVertexBuffer.allocate(NumberOfBuffers, 6 * 3 * sizeof(float));
0263         d->checkersTextureVertexBuffer.allocate(NumberOfBuffers, 6 * 2 * sizeof(float));
0264 
0265         // Create the outline buffer, this buffer will store the outlines of
0266         // tools and will frequently change data
0267         d->outlineVAO.create();
0268         d->outlineVAO.bind();
0269 
0270         glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
0271 
0272         // The outline buffer has a StreamDraw usage pattern, because it changes constantly
0273         d->lineVertexBuffer.create();
0274         d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
0275         d->lineVertexBuffer.bind();
0276         glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
0277     }
0278 
0279     d->canvasInitialized = true;
0280 }
0281 
0282 /**
0283  * Loads all shaders and reports compilation problems
0284  */
0285 void KisOpenGLCanvasRenderer::initializeShaders()
0286 {
0287     KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized);
0288 
0289     delete d->checkerShader;
0290     delete d->solidColorShader;
0291     d->checkerShader = 0;
0292     d->solidColorShader = 0;
0293 
0294     try {
0295         d->checkerShader = d->shaderLoader.loadCheckerShader();
0296         d->solidColorShader = d->shaderLoader.loadSolidColorShader();
0297     } catch (const ShaderLoaderException &e) {
0298         reportFailedShaderCompilation(e.what());
0299     }
0300 
0301     initializeDisplayShader();
0302 }
0303 
0304 void KisOpenGLCanvasRenderer::initializeDisplayShader()
0305 {
0306     KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized);
0307 
0308     bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering;
0309 
0310     delete d->displayShader;
0311     d->displayShader = 0;
0312 
0313     try {
0314         d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering);
0315         d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter;
0316     } catch (const ShaderLoaderException &e) {
0317         reportFailedShaderCompilation(e.what());
0318     }
0319 }
0320 
0321 /**
0322  * Displays a message box telling the user that
0323  * shader compilation failed and turns off OpenGL.
0324  */
0325 void KisOpenGLCanvasRenderer::reportFailedShaderCompilation(const QString &context)
0326 {
0327     KisConfig cfg(false);
0328 
0329     qDebug() << "Shader Compilation Failure: " << context;
0330     // TODO: Should do something else when using QtQuick2
0331     QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"),
0332                           i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context),
0333                           QMessageBox::Close);
0334 
0335     cfg.disableOpenGL();
0336     cfg.setCanvasState("OPENGL_FAILED");
0337 }
0338 
0339 void KisOpenGLCanvasRenderer::updateSize(const QSize &viewportSize)
0340 {
0341     d->viewportDevicePixelSize = viewportSize;
0342     d->pixelAlignedWidgetSize = QSizeF(viewportSize) / devicePixelRatioF();
0343 
0344     // The widget size may be an integer but here we actually want to give
0345     // KisCoordinatesConverter the logical viewport size aligned to device
0346     // pixels.
0347     coordinatesConverter()->setCanvasWidgetSize(d->pixelAlignedWidgetSize);
0348 }
0349 
0350 void KisOpenGLCanvasRenderer::resizeGL(int width, int height)
0351 {
0352     // This is how QOpenGLCanvas sets the FBO and the viewport size. If
0353     // devicePixelRatioF() is non-integral, the result is truncated.
0354     // *Correction*: The FBO size is actually rounded, but the glViewport call
0355     // uses integer truncation and that's what really matters.
0356     int viewportWidth = static_cast<int>(width * devicePixelRatioF());
0357     int viewportHeight = static_cast<int>(height * devicePixelRatioF());
0358 
0359     updateSize(QSize(viewportWidth, viewportHeight));
0360 
0361     if (KisOpenGL::useFBOForToolOutlineRendering()) {
0362         QOpenGLFramebufferObjectFormat format;
0363         if (KisOpenGLModeProber::instance()->useHDRMode()) {
0364             format.setInternalTextureFormat(GL_RGBA16F);
0365         }
0366         d->canvasFBO.reset(new QOpenGLFramebufferObject(d->viewportDevicePixelSize, format));
0367     }
0368 }
0369 
0370 void KisOpenGLCanvasRenderer::paintCanvasOnly(const QRect &canvasImageDirtyRect, const QRect &viewportUpdateRect)
0371 {
0372     if (d->canvasFBO) {
0373         if (!canvasImageDirtyRect.isEmpty()) {
0374             d->canvasFBO->bind();
0375             renderCanvasGL(canvasImageDirtyRect);
0376             d->canvasFBO->release();
0377         }
0378         QRect blitRect;
0379         if (viewportUpdateRect.isEmpty()) {
0380             blitRect = QRect(QPoint(), d->viewportDevicePixelSize);
0381         } else {
0382             const QTransform scale = QTransform::fromScale(1.0, -1.0) * QTransform::fromTranslate(0, d->pixelAlignedWidgetSize.height()) * QTransform::fromScale(devicePixelRatioF(), devicePixelRatioF());
0383             blitRect = scale.mapRect(QRectF(viewportUpdateRect)).toAlignedRect();
0384         }
0385         QOpenGLFramebufferObject::blitFramebuffer(nullptr, blitRect, d->canvasFBO.data(), blitRect, GL_COLOR_BUFFER_BIT, GL_NEAREST);
0386         QOpenGLFramebufferObject::bindDefault();
0387     } else {
0388         QRect fullUpdateRect = canvasImageDirtyRect | viewportUpdateRect;
0389         if (fullUpdateRect.isEmpty()) {
0390             fullUpdateRect = QRect(QPoint(), d->viewportDevicePixelSize);
0391         }
0392         renderCanvasGL(fullUpdateRect);
0393     }
0394 }
0395 
0396 void KisOpenGLCanvasRenderer::paintToolOutline(const KisOptimizedBrushOutline &path, const QRect &viewportUpdateRect, const int thickness)
0397 {
0398     if (!d->solidColorShader->bind()) {
0399         return;
0400     }
0401 
0402     const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
0403 
0404     // setup the mvp transformation
0405     QMatrix4x4 projectionMatrix;
0406     projectionMatrix.setToIdentity();
0407     // FIXME: It may be better to have the projection in device pixel, but
0408     //       this requires introducing a new coordinate system.
0409     projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
0410 
0411     // Set view/projection matrices
0412     QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform());
0413     modelMatrix.optimize();
0414     modelMatrix = projectionMatrix * modelMatrix;
0415     d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix);
0416 
0417     d->solidColorShader->setUniformValue(
0418                 d->solidColorShader->location(Uniform::FragmentColor),
0419                 QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f));
0420 
0421     glEnable(GL_BLEND);
0422     glBlendFuncSeparate(GL_ONE, GL_SRC_COLOR, GL_ONE, GL_ONE);
0423     glBlendEquationSeparate(GL_FUNC_SUBTRACT, GL_FUNC_ADD);
0424 
0425 
0426     if (!viewportUpdateRect.isEmpty()) {
0427         const QRect deviceUpdateRect = widgetToSurface(viewportUpdateRect).toAlignedRect();
0428         glScissor(deviceUpdateRect.x(), deviceUpdateRect.y(), deviceUpdateRect.width(), deviceUpdateRect.height());
0429         glEnable(GL_SCISSOR_TEST);
0430     }
0431 
0432     // Paint the tool outline
0433     if (KisOpenGL::supportsVAO()) {
0434         d->outlineVAO.bind();
0435         d->lineVertexBuffer.bind();
0436     }
0437 
0438     QVector<QVector3D> verticesBuffer;
0439 
0440     if (thickness > 1) {
0441         // Because glLineWidth is not supported on all versions of OpenGL (or rather,
0442         // is limited to 1, as returned by GL_ALIASED_LINE_WIDTH_RANGE),
0443         // we'll instead generate mitered-triangles.
0444 
0445         const qreal halfWidth = (thickness * 0.5) / devicePixelRatioF();
0446         const qreal miterLimit = (5 * thickness) / devicePixelRatioF();
0447 
0448         for (auto it = path.begin(); it != path.end(); ++it) {
0449             const QPolygonF& polygon = *it;
0450 
0451             if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5 * thickness) {
0452                 continue;
0453             }
0454 
0455             int triangleCount = 0;
0456             verticesBuffer.clear();
0457             const bool closed = polygon.isClosed();
0458 
0459             for( int i = 1; i < polygon.count(); i++) {
0460                 bool adjustFirst = closed? true: i > 1;
0461                 bool adjustSecond = closed? true: i + 1 < polygon.count();
0462 
0463                 QPointF p1 = polygon.at(i - 1);
0464                 QPointF p2 = polygon.at(i);
0465                 QPointF normal = p2 - p1;
0466                 normal = KisAlgebra2D::normalize(QPointF(-normal.y(), normal.x()));
0467 
0468                 QPointF c1 = p1 - (normal * halfWidth);
0469                 QPointF c2 = p1 + (normal * halfWidth);
0470                 QPointF c3 = p2 - (normal * halfWidth);
0471                 QPointF c4 = p2 + (normal * halfWidth);
0472 
0473                 // Add miter
0474                 if (adjustFirst) {
0475                     QPointF pPrev = i >= 2 ?
0476                         QPointF(polygon.at(i-2)) :
0477                         QPointF(polygon.at(qMax(polygon.count() - 2, 0)));
0478 
0479                     pPrev = p1 - pPrev;
0480 
0481                     QPointF miter =
0482                         KisAlgebra2D::normalize(normal +
0483                                                 KisAlgebra2D::normalize(
0484                                                     QPointF(-pPrev.y(), pPrev.x())));
0485 
0486                     const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
0487 
0488                     if (KisAlgebra2D::norm((miter * halfWidth) / dot) < miterLimit) {
0489                         c1 = p1 + ((miter * -halfWidth) / dot);
0490                         c2 = p1 + ((miter * halfWidth) / dot);
0491                     }
0492                 }
0493 
0494                 if (adjustSecond) {
0495                     QPointF pNext = i + 1 < polygon.count()? QPointF(polygon.at(i+1))
0496                                                              : QPointF(polygon.at(qMin(polygon.count(), 1)));
0497                     pNext = pNext - p2;
0498                     QPointF miter =
0499                         KisAlgebra2D::normalize(
0500                             normal + KisAlgebra2D::normalize(QPointF(-pNext.y(), pNext.x())));
0501                     const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
0502 
0503                     if (KisAlgebra2D::norm((miter * halfWidth) / dot) < miterLimit) {
0504                         c3 = p2 + ((miter * -halfWidth) / dot);
0505                         c4 = p2 + (miter * halfWidth) / dot;
0506                     }
0507                 }
0508 
0509                 verticesBuffer.append(QVector3D(c1));
0510                 verticesBuffer.append(QVector3D(c3));
0511                 verticesBuffer.append(QVector3D(c2));
0512                 verticesBuffer.append(QVector3D(c4));
0513                 verticesBuffer.append(QVector3D(c2));
0514                 verticesBuffer.append(QVector3D(c3));
0515                 triangleCount += 2;
0516             }
0517 
0518             if (KisOpenGL::supportsVAO()) {
0519                 d->lineVertexBuffer.bind();
0520                 d->lineVertexBuffer.allocate(verticesBuffer.constData(), 3 * verticesBuffer.size() * sizeof(float));
0521             }
0522             else {
0523                 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
0524                 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, verticesBuffer.constData());
0525             }
0526 
0527             glDrawArrays(GL_TRIANGLES, 0, triangleCount * 3);
0528         }
0529     } else {
0530         // Convert every disjointed subpath to a polygon and draw that polygon
0531         for (auto it = path.begin(); it != path.end(); ++it) {
0532             const QPolygonF& polygon = *it;
0533 
0534             if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5) {
0535                 continue;
0536             }
0537 
0538             const int verticesCount = polygon.count();
0539 
0540             if (verticesBuffer.size() < verticesCount) {
0541                 verticesBuffer.resize(verticesCount);
0542             }
0543 
0544             for (int vertIndex = 0; vertIndex < verticesCount; vertIndex++) {
0545                 QPointF point = polygon.at(vertIndex);
0546                 verticesBuffer[vertIndex].setX(point.x());
0547                 verticesBuffer[vertIndex].setY(point.y());
0548             }
0549             if (KisOpenGL::supportsVAO()) {
0550                 d->lineVertexBuffer.bind();
0551                 d->lineVertexBuffer.allocate(verticesBuffer.constData(), 3 * verticesCount * sizeof(float));
0552             }
0553             else {
0554                 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
0555                 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, verticesBuffer.constData());
0556             }
0557 
0558 
0559 
0560             glDrawArrays(GL_LINE_STRIP, 0, verticesCount);
0561         }
0562     }
0563 
0564     if (KisOpenGL::supportsVAO()) {
0565         d->lineVertexBuffer.release();
0566         d->outlineVAO.release();
0567     }
0568 
0569     if (!viewportUpdateRect.isEmpty()) {
0570         glDisable(GL_SCISSOR_TEST);
0571     }
0572 
0573     glBlendEquation(GL_FUNC_ADD);
0574     glBlendFunc(GL_ONE, GL_ZERO);
0575     glDisable(GL_BLEND);
0576 
0577     d->solidColorShader->release();
0578 }
0579 
0580 void KisOpenGLCanvasRenderer::setLodResetInProgress(bool value)
0581 {
0582     d->lodSwitchInProgress = value;
0583 }
0584 
0585 void KisOpenGLCanvasRenderer::drawBackground(const QRect &updateRect)
0586 {
0587     Q_UNUSED(updateRect);
0588 
0589     // Draw the border (that is, clear the whole widget to the border color)
0590     QColor widgetBackgroundColor = borderColor();
0591 
0592     const KoColorSpace *finalColorSpace =
0593             KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(),
0594                                                          d->openGLImageTextures->updateInfoBuilder().destinationColorSpace()->colorDepthId().id(),
0595                                                          d->openGLImageTextures->monitorProfile());
0596 
0597     KoColor convertedBackgroundColor = KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8());
0598     convertedBackgroundColor.convertTo(finalColorSpace);
0599 
0600     QVector<float> channels = QVector<float>(4);
0601     convertedBackgroundColor.colorSpace()->normalisedChannelsValue(convertedBackgroundColor.data(), channels);
0602 
0603 
0604     // Data returned by KoRgbU8ColorSpace comes in the order: blue, green, red.
0605     glClearColor(channels[2], channels[1], channels[0], 1.0);
0606     glClear(GL_COLOR_BUFFER_BIT);
0607 }
0608 
0609 void KisOpenGLCanvasRenderer::drawCheckers(const QRect &updateRect)
0610 {
0611     Q_UNUSED(updateRect);
0612 
0613     if (!d->checkerShader) {
0614         return;
0615     }
0616 
0617     KisCoordinatesConverter *converter = coordinatesConverter();
0618     QTransform textureTransform;
0619     QTransform modelTransform;
0620     QRectF textureRect;
0621     QRectF modelRect;
0622 
0623     const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
0624     QRectF viewportRect;
0625     if (!d->wrapAroundMode) {
0626         viewportRect = converter->imageRectInViewportPixels();
0627     }
0628     else {
0629         const QRectF ir = converter->imageRectInViewportPixels();
0630         viewportRect = converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height()));
0631         if (d->wrapAroundModeAxis == WRAPAROUND_HORIZONTAL) {
0632             viewportRect.setTop(ir.top());
0633             viewportRect.setBottom(ir.bottom());
0634         }
0635         else if (d->wrapAroundModeAxis == WRAPAROUND_VERTICAL) {
0636             viewportRect.setLeft(ir.left());
0637             viewportRect.setRight(ir.right());
0638         }
0639     }
0640 
0641     // TODO: check if it works correctly
0642     if (!canvas()->renderingLimit().isEmpty()) {
0643         const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect();
0644         viewportRect &= vrect;
0645     }
0646 
0647     converter->getOpenGLCheckersInfo(viewportRect,
0648                                      &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers);
0649 
0650     textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE,
0651                                               d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE);
0652 
0653     if (!d->checkerShader->bind()) {
0654         qWarning() << "Could not bind checker shader";
0655         return;
0656     }
0657 
0658     QMatrix4x4 projectionMatrix;
0659     projectionMatrix.setToIdentity();
0660     // FIXME: It may be better to have the projection in device pixel, but
0661     //       this requires introducing a new coordinate system.
0662     projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
0663 
0664     // Set view/projection matrices
0665     QMatrix4x4 modelMatrix(modelTransform);
0666     modelMatrix.optimize();
0667     modelMatrix = projectionMatrix * modelMatrix;
0668     d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix);
0669 
0670     QMatrix4x4 textureMatrix(textureTransform);
0671     d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix);
0672 
0673     //Setup the geometry for rendering
0674     if (KisOpenGL::supportsVAO()) {
0675         KisPaintingTweaks::rectToVertices(d->vertices, modelRect);
0676         QOpenGLBuffer *vertexBuf = d->checkersVertexBuffer.getNextBuffer();
0677 
0678         vertexBuf->bind();
0679         vertexBuf->write(0, d->vertices, 3 * 6 * sizeof(float));
0680         glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
0681 
0682 
0683         KisPaintingTweaks::rectToTexCoords(d->texCoords, textureRect);
0684         QOpenGLBuffer *vertexTextureBuf = d->checkersTextureVertexBuffer.getNextBuffer();
0685 
0686         vertexTextureBuf->bind();
0687         vertexTextureBuf->write(0, d->texCoords, 2 * 6 * sizeof(float));
0688         glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
0689     }
0690     else {
0691         KisPaintingTweaks::rectToVertices(d->vertices, modelRect);
0692         d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
0693         d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
0694 
0695         KisPaintingTweaks::rectToTexCoords(d->texCoords, textureRect);
0696         d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
0697         d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords);
0698     }
0699 
0700     // render checkers
0701     glActiveTexture(GL_TEXTURE0);
0702     glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture());
0703 
0704     glDrawArrays(GL_TRIANGLES, 0, 6);
0705 
0706     glBindTexture(GL_TEXTURE_2D, 0);
0707     d->checkerShader->release();
0708     glBindBuffer(GL_ARRAY_BUFFER, 0);
0709 }
0710 
0711 void KisOpenGLCanvasRenderer::drawGrid(const QRect &updateRect)
0712 {
0713     if (!d->solidColorShader->bind()) {
0714         return;
0715     }
0716 
0717     const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
0718 
0719     QMatrix4x4 projectionMatrix;
0720     projectionMatrix.setToIdentity();
0721     // FIXME: It may be better to have the projection in device pixel, but
0722     //       this requires introducing a new coordinate system.
0723     projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
0724 
0725     // Set view/projection matrices
0726     QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform());
0727     modelMatrix.optimize();
0728     modelMatrix = projectionMatrix * modelMatrix;
0729     d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix);
0730 
0731     glEnable(GL_BLEND);
0732     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0733 
0734     d->solidColorShader->setUniformValue(
0735                 d->solidColorShader->location(Uniform::FragmentColor),
0736                 QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f));
0737 
0738     QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
0739     QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect));
0740     QRect wr = widgetRectInImagePixels.toAlignedRect();
0741 
0742     if (!d->wrapAroundMode) {
0743         wr &= d->openGLImageTextures->storedImageBounds();
0744     }
0745 
0746     if (!updateRect.isEmpty()) {
0747         const QRect updateRectInImagePixels = coordinatesConverter()->widgetToImage(updateRect).toAlignedRect();
0748         wr &= updateRectInImagePixels;
0749     }
0750 
0751     QPoint topLeftCorner = wr.topLeft();
0752     QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1);
0753     QVector<QVector3D> grid;
0754 
0755     for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) {
0756         grid.append(QVector3D(i, topLeftCorner.y(), 0));
0757         grid.append(QVector3D(i, bottomRightCorner.y(), 0));
0758     }
0759     for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) {
0760         grid.append(QVector3D(topLeftCorner.x(), i, 0));
0761         grid.append(QVector3D(bottomRightCorner.x(), i, 0));
0762     }
0763 
0764     if (KisOpenGL::supportsVAO()) {
0765         d->outlineVAO.bind();
0766         d->lineVertexBuffer.bind();
0767         d->lineVertexBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float));
0768     }
0769     else {
0770         d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
0771         d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData());
0772     }
0773 
0774     glDrawArrays(GL_LINES, 0, grid.size());
0775 
0776     if (KisOpenGL::supportsVAO()) {
0777         d->lineVertexBuffer.release();
0778         d->outlineVAO.release();
0779     }
0780 
0781     d->solidColorShader->release();
0782     glDisable(GL_BLEND);
0783 }
0784 
0785 void KisOpenGLCanvasRenderer::drawImage(const QRect &updateRect)
0786 {
0787     if (!d->displayShader) {
0788         return;
0789     }
0790 
0791     glEnable(GL_BLEND);
0792     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0793 
0794     KisCoordinatesConverter *converter = coordinatesConverter();
0795 
0796     d->displayShader->bind();
0797 
0798     const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
0799 
0800     QMatrix4x4 textureMatrix;
0801     textureMatrix.setToIdentity();
0802     d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix);
0803 
0804     QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
0805 
0806     if (!updateRect.isEmpty()) {
0807         widgetRect &= updateRect;
0808     }
0809 
0810     QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect));
0811 
0812     const QRect renderingLimit = canvas()->renderingLimit();
0813 
0814     if (!renderingLimit.isEmpty()) {
0815         widgetRectInImagePixels &= renderingLimit;
0816     }
0817 
0818     qreal scaleX, scaleY;
0819     converter->imagePhysicalScale(&scaleX, &scaleY);
0820 
0821     d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX);
0822     d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize());
0823 
0824     QRect ir = d->openGLImageTextures->storedImageBounds();
0825     QRect wr = widgetRectInImagePixels.toAlignedRect();
0826 
0827     if (!d->wrapAroundMode) {
0828         // if we don't want to paint wrapping images, just limit the
0829         // processing area, and the code will handle all the rest
0830         wr &= ir;
0831     }
0832     else if (d->wrapAroundModeAxis == WRAPAROUND_HORIZONTAL) {
0833         wr.setTop(ir.top());
0834         wr.setBottom(ir.bottom());
0835     }
0836     else if (d->wrapAroundModeAxis == WRAPAROUND_VERTICAL) {
0837         wr.setLeft(ir.left());
0838         wr.setRight(ir.right());
0839     }
0840 
0841     const int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir);
0842     const int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir);
0843     const int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir);
0844     const int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir);
0845 
0846     const int minColumn = d->openGLImageTextures->xToCol(ir.left());
0847     const int maxColumn = d->openGLImageTextures->xToCol(ir.right());
0848     const int minRow = d->openGLImageTextures->yToRow(ir.top());
0849     const int maxRow = d->openGLImageTextures->yToRow(ir.bottom());
0850 
0851     const int imageColumns = maxColumn - minColumn + 1;
0852     const int imageRows = maxRow - minRow + 1;
0853 
0854     if (d->displayFilter) {
0855         d->displayFilter->setupTextures(this, d->displayShader);
0856     }
0857 
0858     const int firstCloneX = qFloor(qreal(firstColumn) / imageColumns);
0859     const int lastCloneX = qFloor(qreal(lastColumn) / imageColumns);
0860     const int firstCloneY = qFloor(qreal(firstRow) / imageRows);
0861     const int lastCloneY = qFloor(qreal(lastRow) / imageRows);
0862 
0863     for (int cloneY = firstCloneY; cloneY <= lastCloneY; cloneY++) {
0864         for (int cloneX = firstCloneX; cloneX <= lastCloneX; cloneX++) {
0865 
0866             const int localFirstCol = cloneX == firstCloneX ? KisAlgebra2D::wrapValue(firstColumn, imageColumns) : 0;
0867             const int localLastCol = cloneX == lastCloneX ? KisAlgebra2D::wrapValue(lastColumn, imageColumns) : imageColumns - 1;
0868 
0869             const int localFirstRow = cloneY == firstCloneY ? KisAlgebra2D::wrapValue(firstRow, imageRows) : 0;
0870             const int localLastRow = cloneY == lastCloneY ? KisAlgebra2D::wrapValue(lastRow, imageRows) : imageRows - 1;
0871 
0872             drawImageTiles(localFirstCol, localLastCol,
0873                            localFirstRow, localLastRow,
0874                            scaleX, scaleY, QPoint(cloneX, cloneY));
0875         }
0876     }
0877 
0878     d->displayShader->release();
0879 
0880     glDisable(GL_BLEND);
0881 }
0882 
0883 void KisOpenGLCanvasRenderer::drawImageTiles(int firstCol, int lastCol, int firstRow, int lastRow, qreal scaleX, qreal scaleY, const QPoint &wrapAroundOffset)
0884 {
0885     KisCoordinatesConverter *converter = coordinatesConverter();
0886     const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
0887 
0888     QMatrix4x4 projectionMatrix;
0889     projectionMatrix.setToIdentity();
0890     // FIXME: It may be better to have the projection in device pixel, but
0891     //       this requires introducing a new coordinate system.
0892     projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
0893 
0894     QTransform modelTransform = converter->imageToWidgetTransform();
0895 
0896     if (!wrapAroundOffset.isNull()) {
0897         const QRect ir = d->openGLImageTextures->storedImageBounds();
0898 
0899         const QTransform wrapAroundTranslate = QTransform::fromTranslate(ir.width() * wrapAroundOffset.x(),
0900                                                                    ir.height() * wrapAroundOffset.y());
0901         modelTransform = wrapAroundTranslate * modelTransform;
0902     }
0903 
0904     // Set view/projection matrices
0905     QMatrix4x4 modelMatrix(modelTransform);
0906     modelMatrix.optimize();
0907     modelMatrix = projectionMatrix * modelMatrix;
0908     d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix);
0909 
0910     int lastTileLodPlane = -1;
0911 
0912     for (int col = firstCol; col <= lastCol; col++) {
0913         for (int row = firstRow; row <= lastRow; row++) {
0914 
0915             KisTextureTile *tile =
0916                     d->openGLImageTextures->getTextureTileCR(col, row);
0917 
0918             if (!tile) {
0919                 warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet.";
0920                 continue;
0921             }
0922 
0923             //Setup the geometry for rendering
0924             if (KisOpenGL::supportsVAO()) {
0925                 const int tileIndex = d->openGLImageTextures->getTextureBufferIndexCR(col, row);
0926 
0927                 const int vertexRectSize = 6 * 3 * sizeof(float);
0928                 d->openGLImageTextures->tileVertexBuffer()->bind();
0929                 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * vertexRectSize));
0930 
0931                 const int textureRectSize = 6 * 2 * sizeof(float);
0932                 d->openGLImageTextures->tileTexCoordBuffer()->bind();
0933                 glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * textureRectSize));
0934 
0935             } else {
0936 
0937                 const QRectF textureRect = tile->tileRectInTexturePixels();
0938                 const QRectF modelRect = tile->tileRectInImagePixels();
0939 
0940                 KisPaintingTweaks::rectToVertices(d->vertices, modelRect);
0941                 d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
0942                 d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
0943 
0944                 KisPaintingTweaks::rectToTexCoords(d->texCoords, textureRect);
0945                 d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
0946                 d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords);
0947             }
0948 
0949             glActiveTexture(GL_TEXTURE0);
0950 
0951             // switching uniform is a rather expensive operation on macOS, so we change it only
0952             // when it is really needed
0953             const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress);
0954             if (d->displayShader->location(Uniform::FixedLodLevel) >= 0 &&
0955                 (lastTileLodPlane < 0 || lastTileLodPlane != currentLodPlane)) {
0956 
0957                 d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel),
0958                                                   (GLfloat) currentLodPlane);
0959             }
0960 
0961             if (currentLodPlane > 0) {
0962                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
0963             } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) {
0964                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
0965                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
0966             } else {
0967                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0968 
0969                 switch(d->filterMode) {
0970                 case KisOpenGL::NearestFilterMode:
0971                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
0972                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
0973                     break;
0974                 case KisOpenGL::BilinearFilterMode:
0975                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0976                     break;
0977                 case KisOpenGL::TrilinearFilterMode:
0978                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
0979                     break;
0980                 case KisOpenGL::HighQualityFiltering:
0981                     if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) {
0982                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
0983                     } else {
0984                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0985                     }
0986                     break;
0987                 }
0988             }
0989 
0990             glDrawArrays(GL_TRIANGLES, 0, 6);
0991         }
0992     }
0993 
0994     glBindTexture(GL_TEXTURE_2D, 0);
0995     glBindBuffer(GL_ARRAY_BUFFER, 0);
0996 }
0997 
0998 void KisOpenGLCanvasRenderer::updateConfig()
0999 {
1000     KisConfig cfg(true);
1001     d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast<GLfloat>(cfg.checkSize());
1002     d->scrollCheckers = cfg.scrollCheckers();
1003 
1004     d->openGLImageTextures->generateCheckerTexture(KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()));
1005     d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels());
1006     d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode();
1007 
1008     updateCursorColor();
1009 }
1010 
1011 void KisOpenGLCanvasRenderer::updateCursorColor()
1012 {
1013     KisConfig cfg(true);
1014     bool useSeparateEraserCursor = cfg.separateEraserCursor() &&
1015             canvas()->resourceManager()->resource(KoCanvasResource::CurrentEffectiveCompositeOp).toString() == COMPOSITE_ERASE;
1016 
1017     d->cursorColor = (!useSeparateEraserCursor) ? cfg.getCursorMainColor() : cfg.getEraserCursorMainColor();
1018 }
1019 
1020 void KisOpenGLCanvasRenderer::updatePixelGridMode()
1021 {
1022     KisConfig cfg(true);
1023 
1024     d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold();
1025     d->pixelGridEnabled = cfg.pixelGridEnabled();
1026     d->gridColor = cfg.getPixelGridColor();
1027 }
1028 
1029 QRectF KisOpenGLCanvasRenderer::widgetToSurface(const QRectF &rc)
1030 {
1031     const qreal ratio = devicePixelRatioF();
1032 
1033     return QRectF(rc.x() * ratio,
1034                   (d->pixelAlignedWidgetSize.height() - rc.y() - rc.height()) * ratio,
1035                   rc.width() * ratio,
1036                   rc.height() * ratio);
1037 }
1038 
1039 QRectF KisOpenGLCanvasRenderer::surfaceToWidget(const QRectF &rc)
1040 {
1041     const qreal ratio = devicePixelRatioF();
1042 
1043     return QRectF(rc.x() / ratio,
1044                   d->pixelAlignedWidgetSize.height() - (rc.y() + rc.height()) / ratio,
1045                   rc.width() / ratio,
1046                   rc.height() / ratio);
1047 }
1048 
1049 
1050 void KisOpenGLCanvasRenderer::renderCanvasGL(const QRect &updateRect)
1051 {
1052     if ((d->displayFilter && d->displayFilter->updateShader()) ||
1053         (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) {
1054 
1055         KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized);
1056 
1057         d->canvasInitialized = false; // TODO: check if actually needed?
1058         initializeDisplayShader();
1059         d->canvasInitialized = true;
1060     }
1061 
1062     if (KisOpenGL::supportsVAO()) {
1063         d->quadVAO.bind();
1064     }
1065 
1066     QRect alignedUpdateRect = updateRect;
1067 
1068     if (!updateRect.isEmpty()) {
1069         const QRect deviceUpdateRect = widgetToSurface(updateRect).toAlignedRect();
1070         alignedUpdateRect = surfaceToWidget(deviceUpdateRect).toAlignedRect();
1071 
1072         glScissor(deviceUpdateRect.x(), deviceUpdateRect.y(), deviceUpdateRect.width(), deviceUpdateRect.height());
1073         glEnable(GL_SCISSOR_TEST);
1074     }
1075 
1076     drawBackground(alignedUpdateRect);
1077     drawCheckers(alignedUpdateRect);
1078     drawImage(alignedUpdateRect);
1079 
1080     if ((coordinatesConverter()->effectivePhysicalZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) {
1081         drawGrid(alignedUpdateRect);
1082     }
1083 
1084     if (!updateRect.isEmpty()) {
1085         glDisable(GL_SCISSOR_TEST);
1086     }
1087 
1088     if (KisOpenGL::supportsVAO()) {
1089         d->quadVAO.release();
1090     }
1091 }
1092 
1093 void KisOpenGLCanvasRenderer::setDisplayColorConverter(KisDisplayColorConverter *colorConverter)
1094 {
1095     d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(),
1096                                               colorConverter->renderingIntent(),
1097                                               colorConverter->conversionFlags());
1098 }
1099 
1100 void KisOpenGLCanvasRenderer::channelSelectionChanged(const QBitArray &channelFlags)
1101 {
1102     d->openGLImageTextures->setChannelFlags(channelFlags);
1103 }
1104 
1105 
1106 void KisOpenGLCanvasRenderer::finishResizingImage(qint32 w, qint32 h)
1107 {
1108     if (d->canvasInitialized) {
1109         d->openGLImageTextures->slotImageSizeChanged(w, h);
1110     }
1111 }
1112 
1113 KisUpdateInfoSP KisOpenGLCanvasRenderer::startUpdateCanvasProjection(const QRect & rc)
1114 {
1115     if (canvas()->proofingConfigUpdated()) {
1116         d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
1117         canvas()->setProofingConfigUpdated(false);
1118     }
1119     return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image());
1120 }
1121 
1122 
1123 QRect KisOpenGLCanvasRenderer::updateCanvasProjection(KisUpdateInfoSP info)
1124 {
1125     // See KisQPainterCanvas::updateCanvasProjection for more info
1126     bool isOpenGLUpdateInfo = dynamic_cast<KisOpenGLUpdateInfo*>(info.data());
1127     if (isOpenGLUpdateInfo) {
1128         d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress);
1129     }
1130 
1131     const QRect dirty = kisGrowRect(coordinatesConverter()->imageToWidget(info->dirtyImageRect()).toAlignedRect(), 2);
1132     return dirty;
1133 }
1134 
1135 KisOpenGLImageTexturesSP KisOpenGLCanvasRenderer::openGLImageTextures() const
1136 {
1137     return d->openGLImageTextures;
1138 }