Krita Source Code Documentation
Loading...
Searching...
No Matches
KisOpenGLCanvasRenderer.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2006-2013 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#define GL_GLEXT_PROTOTYPES
9
11
12#include "kis_algebra_2d.h"
14#include "canvas/kis_canvas2.h"
18#include "KisOpenGLModeProber.h"
20#include "kis_config.h"
21#include "kis_debug.h"
22
23#include <QPainter>
24#include <QPainterPath>
25#include <QOpenGLPaintDevice>
26#include <QPointF>
27#include <QTransform>
28#include <QThread>
29#include <QFile>
30#include <QOpenGLShaderProgram>
31#include <QOpenGLVertexArrayObject>
32#include <QOpenGLBuffer>
33#include <QOpenGLFramebufferObject>
34#include <QOpenGLFramebufferObjectFormat>
35#include <QMessageBox>
36#include <QVector3D>
40#include "kis_painting_tweaks.h"
42#include <KisDisplayConfig.h>
43
44#include <config-ocio.h>
45
46#define NEAR_VAL -1000.0
47#define FAR_VAL 1000.0
48
49#ifndef GL_CLAMP_TO_EDGE
50#define GL_CLAMP_TO_EDGE 0x812F
51#endif
52
53#define PROGRAM_VERTEX_ATTRIBUTE 0
54#define PROGRAM_TEXCOORD_ATTRIBUTE 1
55
56// These buffers are used only for painting checkers,
57// so we can keep the number really low
58static constexpr int NumberOfBuffers = 2;
59
61{
62public:
64 delete displayShader;
65 delete checkerShader;
66 delete solidColorShader;
67
68 delete canvasBridge;
69 }
70
71 bool canvasInitialized{false};
72
74
79
80 QScopedPointer<QOpenGLFramebufferObject> canvasFBO;
81
83
86
90
91 bool wrapAroundMode{false};
93
94 // Stores a quad for drawing the canvas
95 QOpenGLVertexArrayObject quadVAO;
96
99
100 // Stores data for drawing tool outlines
101 QOpenGLVertexArrayObject outlineVAO;
102 QOpenGLBuffer lineVertexBuffer;
103
104 QVector3D vertices[6];
105 QVector2D texCoords[6];
106
109 QColor gridColor;
111
113
117
118 int xToColWithWrapCompensation(int x, const QRect &imageRect) {
119 int firstImageColumn = openGLImageTextures->xToCol(imageRect.left());
120 int lastImageColumn = openGLImageTextures->xToCol(imageRect.right());
121
122 int colsPerImage = lastImageColumn - firstImageColumn + 1;
123 int numWraps = floor(qreal(x) / imageRect.width());
124 int remainder = x - imageRect.width() * numWraps;
125
126 return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder);
127 }
128
129 int yToRowWithWrapCompensation(int y, const QRect &imageRect) {
130 int firstImageRow = openGLImageTextures->yToRow(imageRect.top());
131 int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom());
132
133 int rowsPerImage = lastImageRow - firstImageRow + 1;
134 int numWraps = floor(qreal(y) / imageRect.height());
135 int remainder = y - imageRect.height() * numWraps;
136
137 return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder);
138 }
139
140};
141
143 KisImageWSP image,
144 const KisDisplayConfig &displayConfig,
146 : d(new Private())
147{
148 d->canvasBridge = canvasBridge;
149
150 const KisDisplayConfig &config = displayConfig;
151
154 config.profile,
155 config.intent,
156 config.conversionFlags);
157
158
159 setDisplayFilterImpl(displayFilter, true);
160}
161
166
171
172QOpenGLContext *KisOpenGLCanvasRenderer::context() const
173{
174 return d->canvasBridge->openglContext();
175}
176
181
186
188{
189 return d->canvasBridge->borderColor();
190}
191
196
198{
199 bool needsInternalColorManagement =
200 !displayFilter || displayFilter->useInternalColorManagement();
201
202 bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement);
203
204 d->displayFilter = displayFilter;
205
206 if (!initializing && needsFullRefresh) {
207 canvas()->startUpdateInPatches(canvas()->image()->bounds());
208 }
209 else if (!initializing) {
210 canvas()->updateCanvas();
211 }
212}
213
215{
216 // FIXME: on color space change the data is refetched multiple
217 // times by different actors!
218
220 canvas()->startUpdateInPatches(canvas()->image()->bounds());
221 }
222}
223
228
233
238
243
245{
247 initializeOpenGLFunctions();
248
249 KisConfig cfg(true);
250 d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
251 d->openGLImageTextures->initGL(context()->functions());
253
255
256 // If we support OpenGL 3.0, then prepare our VAOs and VBOs for drawing
258 d->quadVAO.create();
259 d->quadVAO.bind();
260
261 glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
262 glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE);
263
264 d->checkersVertexBuffer.allocate(NumberOfBuffers, 6 * 3 * sizeof(float));
265 d->checkersTextureVertexBuffer.allocate(NumberOfBuffers, 6 * 2 * sizeof(float));
266
267 // Create the outline buffer, this buffer will store the outlines of
268 // tools and will frequently change data
269 d->outlineVAO.create();
270 d->outlineVAO.bind();
271
272 glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
273
274 // The outline buffer has a StreamDraw usage pattern, because it changes constantly
275 d->lineVertexBuffer.create();
276 d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
277 d->lineVertexBuffer.bind();
278 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
279 }
280
281 d->canvasInitialized = true;
282}
283
305
307{
309
310 bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering;
311
312 delete d->displayShader;
313 d->displayShader = 0;
314
315 try {
316 d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering);
318 } catch (const ShaderLoaderException &e) {
320 }
321}
322
328{
329 KisConfig cfg(false);
330
331 qDebug() << "Shader Compilation Failure: " << context;
332 // TODO: Should do something else when using QtQuick2
333 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"),
334 i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context),
335 QMessageBox::Close);
336
337 cfg.disableOpenGL();
338 cfg.setCanvasState("OPENGL_FAILED");
339}
340
341void KisOpenGLCanvasRenderer::resizeGL(int width, int height)
342{
343 {
344 // just a sanity check!
345 //
346 // This is how QOpenGLCanvas sets the FBO and the viewport size. If
347 // devicePixelRatioF() is non-integral, the result is truncated.
348 // *Correction*: The FBO size is actually rounded, but the glViewport call
349 // uses integer truncation and that's what really matters.
350 int viewportWidth = static_cast<int>(width * devicePixelRatioF());
351 int viewportHeight = static_cast<int>(height * devicePixelRatioF());
352
353 // We expect the size to be adjusted in the converter at the higher
354 // level of hierarchy. It happens in KisCanvasControllerWidget, which
355 // then explicitly resizes the canvas widget.
356 KIS_SAFE_ASSERT_RECOVER_NOOP(QSize(viewportWidth, viewportHeight) == coordinatesConverter()->viewportDevicePixelSize());
357 }
358
361
363 QOpenGLFramebufferObjectFormat format;
364 format.setInternalTextureFormat(d->canvasBridge->internalTextureFormat());
365 d->canvasFBO.reset(new QOpenGLFramebufferObject(d->viewportDevicePixelSize, format));
366 }
367}
368
369void KisOpenGLCanvasRenderer::paintCanvasOnly(const QRect &canvasImageDirtyRect, const QRect &viewportUpdateRect)
370{
371 if (d->canvasFBO) {
372 if (!canvasImageDirtyRect.isEmpty()) {
373 d->canvasFBO->bind();
374 renderCanvasGL(canvasImageDirtyRect);
375 d->canvasFBO->release();
376 }
377 QRect blitRect;
378 if (viewportUpdateRect.isEmpty()) {
379 blitRect = QRect(QPoint(), d->viewportDevicePixelSize);
380 } else {
381 const QTransform scale = QTransform::fromScale(1.0, -1.0) * QTransform::fromTranslate(0, d->pixelAlignedWidgetSize.height()) * QTransform::fromScale(devicePixelRatioF(), devicePixelRatioF());
382 blitRect = scale.mapRect(QRectF(viewportUpdateRect)).toAlignedRect();
383 }
384 QOpenGLFramebufferObject::blitFramebuffer(nullptr, blitRect, d->canvasFBO.data(), blitRect, GL_COLOR_BUFFER_BIT, GL_NEAREST);
385 QOpenGLFramebufferObject::bindDefault();
386 } else {
387 QRect fullUpdateRect = canvasImageDirtyRect | viewportUpdateRect;
388 if (fullUpdateRect.isEmpty()) {
389 fullUpdateRect = QRect(QPoint(), d->viewportDevicePixelSize);
390 }
391 renderCanvasGL(fullUpdateRect);
392 }
393}
394
395void KisOpenGLCanvasRenderer::paintToolOutline(const KisOptimizedBrushOutline &path, const QRect &viewportUpdateRect, const int thickness)
396{
397 if (!d->solidColorShader->bind()) {
398 return;
399 }
400
401 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
402
403 // setup the mvp transformation
404 QMatrix4x4 projectionMatrix;
405 projectionMatrix.setToIdentity();
406 // FIXME: It may be better to have the projection in device pixel, but
407 // this requires introducing a new coordinate system.
408 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
409
410 // Set view/projection matrices
411 QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform());
412 modelMatrix.optimize();
413 modelMatrix = projectionMatrix * modelMatrix;
415
416 d->solidColorShader->setUniformValue(
418 QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f));
419
420 glEnable(GL_BLEND);
421 glBlendFuncSeparate(GL_ONE, GL_SRC_COLOR, GL_ONE, GL_ONE);
422 glBlendEquationSeparate(GL_FUNC_SUBTRACT, GL_FUNC_ADD);
423
424
425 if (!viewportUpdateRect.isEmpty()) {
426 const QRect deviceUpdateRect = widgetToSurface(viewportUpdateRect).toAlignedRect();
427 glScissor(deviceUpdateRect.x(), deviceUpdateRect.y(), deviceUpdateRect.width(), deviceUpdateRect.height());
428 glEnable(GL_SCISSOR_TEST);
429 }
430
431 // Paint the tool outline
433 d->outlineVAO.bind();
434 d->lineVertexBuffer.bind();
435 }
436
437 QVector<QVector3D> verticesBuffer;
438
439 if (thickness > 1) {
440 // Because glLineWidth is not supported on all versions of OpenGL (or rather,
441 // is limited to 1, as returned by GL_ALIASED_LINE_WIDTH_RANGE),
442 // we'll instead generate mitered-triangles.
443
444 const qreal halfWidth = (thickness * 0.5) / devicePixelRatioF();
445 const qreal miterLimit = (5 * thickness) / devicePixelRatioF();
446
447 for (auto it = path.begin(); it != path.end(); ++it) {
448 const QPolygonF& polygon = *it;
449
450 if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5 * thickness) {
451 continue;
452 }
453
454 int triangleCount = 0;
455 verticesBuffer.clear();
456 const bool closed = polygon.isClosed();
457
458 for( int i = 1; i < polygon.count(); i++) {
459 bool adjustFirst = closed? true: i > 1;
460 bool adjustSecond = closed? true: i + 1 < polygon.count();
461
462 QPointF p1 = polygon.at(i - 1);
463 QPointF p2 = polygon.at(i);
464 QPointF normal = p2 - p1;
465 normal = KisAlgebra2D::normalize(QPointF(-normal.y(), normal.x()));
466
467 QPointF c1 = p1 - (normal * halfWidth);
468 QPointF c2 = p1 + (normal * halfWidth);
469 QPointF c3 = p2 - (normal * halfWidth);
470 QPointF c4 = p2 + (normal * halfWidth);
471
472 // Add miter
473 if (adjustFirst) {
474 QPointF pPrev = i >= 2 ?
475 QPointF(polygon.at(i-2)) :
476 QPointF(polygon.at(qMax(polygon.count() - 2, 0)));
477
478 pPrev = p1 - pPrev;
479
480 QPointF miter =
483 QPointF(-pPrev.y(), pPrev.x())));
484
485 const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
486
487 if (KisAlgebra2D::norm((miter * halfWidth) / dot) < miterLimit) {
488 c1 = p1 + ((miter * -halfWidth) / dot);
489 c2 = p1 + ((miter * halfWidth) / dot);
490 }
491 }
492
493 if (adjustSecond) {
494 QPointF pNext = i + 1 < polygon.count()? QPointF(polygon.at(i+1))
495 : QPointF(polygon.at(qMin(polygon.count(), 1)));
496 pNext = pNext - p2;
497 QPointF miter =
499 normal + KisAlgebra2D::normalize(QPointF(-pNext.y(), pNext.x())));
500 const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
501
502 if (KisAlgebra2D::norm((miter * halfWidth) / dot) < miterLimit) {
503 c3 = p2 + ((miter * -halfWidth) / dot);
504 c4 = p2 + (miter * halfWidth) / dot;
505 }
506 }
507
508 verticesBuffer.append(QVector3D(c1));
509 verticesBuffer.append(QVector3D(c3));
510 verticesBuffer.append(QVector3D(c2));
511 verticesBuffer.append(QVector3D(c4));
512 verticesBuffer.append(QVector3D(c2));
513 verticesBuffer.append(QVector3D(c3));
514 triangleCount += 2;
515 }
516
518 d->lineVertexBuffer.bind();
519 d->lineVertexBuffer.allocate(verticesBuffer.constData(), 3 * verticesBuffer.size() * sizeof(float));
520 }
521 else {
522 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
523 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, verticesBuffer.constData());
524 }
525
526 glDrawArrays(GL_TRIANGLES, 0, triangleCount * 3);
527 }
528 } else {
529 // Convert every disjointed subpath to a polygon and draw that polygon
530 for (auto it = path.begin(); it != path.end(); ++it) {
531 const QPolygonF& polygon = *it;
532
533 if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5) {
534 continue;
535 }
536
537 const int verticesCount = polygon.count();
538
539 if (verticesBuffer.size() < verticesCount) {
540 verticesBuffer.resize(verticesCount);
541 }
542
543 for (int vertIndex = 0; vertIndex < verticesCount; vertIndex++) {
544 QPointF point = polygon.at(vertIndex);
545 verticesBuffer[vertIndex].setX(point.x());
546 verticesBuffer[vertIndex].setY(point.y());
547 }
549 d->lineVertexBuffer.bind();
550 d->lineVertexBuffer.allocate(verticesBuffer.constData(), 3 * verticesCount * sizeof(float));
551 }
552 else {
553 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
554 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, verticesBuffer.constData());
555 }
556
557
558
559 glDrawArrays(GL_LINE_STRIP, 0, verticesCount);
560 }
561 }
562
564 d->lineVertexBuffer.release();
565 d->outlineVAO.release();
566 }
567
568 if (!viewportUpdateRect.isEmpty()) {
569 glDisable(GL_SCISSOR_TEST);
570 }
571
572 glBlendEquation(GL_FUNC_ADD);
573 glBlendFunc(GL_ONE, GL_ZERO);
574 glDisable(GL_BLEND);
575
576 d->solidColorShader->release();
577}
578
583
584void KisOpenGLCanvasRenderer::drawBackground(const QRect &updateRect)
585{
586 Q_UNUSED(updateRect);
587
588 // Draw the border (that is, clear the whole widget to the border color)
589 QColor widgetBackgroundColor = borderColor();
590
591 const KoColorSpace *finalColorSpace =
595
596 KoColor convertedBackgroundColor = KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8());
597 convertedBackgroundColor.convertTo(finalColorSpace);
598
599 QVector<float> channels = QVector<float>(4);
600 convertedBackgroundColor.colorSpace()->normalisedChannelsValue(convertedBackgroundColor.data(), channels);
601
602
603 // Data returned by KoRgbU8ColorSpace comes in the order: blue, green, red.
604 glClearColor(channels[2], channels[1], channels[0], 1.0);
605 glClear(GL_COLOR_BUFFER_BIT);
606}
607
608void KisOpenGLCanvasRenderer::drawCheckers(const QRect &updateRect)
609{
610 Q_UNUSED(updateRect);
611
612 if (!d->checkerShader) {
613 return;
614 }
615
617 QTransform textureTransform;
618 QTransform modelTransform;
619 QRectF textureRect;
620 QRectF modelRect;
621
622 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
623 QRectF viewportRect;
624 if (!d->wrapAroundMode) {
625 viewportRect = converter->imageRectInViewportPixels();
626 }
627 else {
628 const QRectF ir = converter->imageRectInViewportPixels();
629 viewportRect = converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height()));
631 viewportRect.setTop(ir.top());
632 viewportRect.setBottom(ir.bottom());
633 }
635 viewportRect.setLeft(ir.left());
636 viewportRect.setRight(ir.right());
637 }
638 }
639
640 // TODO: check if it works correctly
641 if (!canvas()->renderingLimit().isEmpty()) {
642 const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect();
643 viewportRect &= vrect;
644 }
645
646 converter->getOpenGLCheckersInfo(viewportRect,
647 &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers);
648
649 textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE,
651
652 if (!d->checkerShader->bind()) {
653 qWarning() << "Could not bind checker shader";
654 return;
655 }
656
657 QMatrix4x4 projectionMatrix;
658 projectionMatrix.setToIdentity();
659 // FIXME: It may be better to have the projection in device pixel, but
660 // this requires introducing a new coordinate system.
661 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
662
663 // Set view/projection matrices
664 QMatrix4x4 modelMatrix(modelTransform);
665 modelMatrix.optimize();
666 modelMatrix = projectionMatrix * modelMatrix;
667 d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix);
668
669 QMatrix4x4 textureMatrix(textureTransform);
670 d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix);
671
672 //Setup the geometry for rendering
675 QOpenGLBuffer *vertexBuf = d->checkersVertexBuffer.getNextBuffer();
676
677 vertexBuf->bind();
678 vertexBuf->write(0, d->vertices, 3 * 6 * sizeof(float));
679 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
680
681
683 QOpenGLBuffer *vertexTextureBuf = d->checkersTextureVertexBuffer.getNextBuffer();
684
685 vertexTextureBuf->bind();
686 vertexTextureBuf->write(0, d->texCoords, 2 * 6 * sizeof(float));
687 glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
688 }
689 else {
691 d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
692 d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
693
695 d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
697 }
698
699 // render checkers
700 glActiveTexture(GL_TEXTURE0);
701 glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture());
702
703 glDrawArrays(GL_TRIANGLES, 0, 6);
704
705 glBindTexture(GL_TEXTURE_2D, 0);
706 d->checkerShader->release();
707 glBindBuffer(GL_ARRAY_BUFFER, 0);
708}
709
710void KisOpenGLCanvasRenderer::drawGrid(const QRect &updateRect)
711{
712 if (!d->solidColorShader->bind()) {
713 return;
714 }
715
716 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
717
718 QMatrix4x4 projectionMatrix;
719 projectionMatrix.setToIdentity();
720 // FIXME: It may be better to have the projection in device pixel, but
721 // this requires introducing a new coordinate system.
722 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
723
724 // Set view/projection matrices
725 QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform());
726 modelMatrix.optimize();
727 modelMatrix = projectionMatrix * modelMatrix;
729
730 glEnable(GL_BLEND);
731 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
732
733 d->solidColorShader->setUniformValue(
735 QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f));
736
737 QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
738 QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect));
739 QRect wr = widgetRectInImagePixels.toAlignedRect();
740
741 if (!d->wrapAroundMode) {
743 }
744
745 if (!updateRect.isEmpty()) {
746 const QRect updateRectInImagePixels = coordinatesConverter()->widgetToImage(updateRect).toAlignedRect();
747 wr &= updateRectInImagePixels;
748 }
749
750 QPoint topLeftCorner = wr.topLeft();
751 QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1);
753
754 for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) {
755 grid.append(QVector3D(i, topLeftCorner.y(), 0));
756 grid.append(QVector3D(i, bottomRightCorner.y(), 0));
757 }
758 for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) {
759 grid.append(QVector3D(topLeftCorner.x(), i, 0));
760 grid.append(QVector3D(bottomRightCorner.x(), i, 0));
761 }
762
764 d->outlineVAO.bind();
765 d->lineVertexBuffer.bind();
766 d->lineVertexBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float));
767 }
768 else {
769 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
770 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData());
771 }
772
773 glDrawArrays(GL_LINES, 0, grid.size());
774
776 d->lineVertexBuffer.release();
777 d->outlineVAO.release();
778 }
779
780 d->solidColorShader->release();
781 glDisable(GL_BLEND);
782}
783
784void KisOpenGLCanvasRenderer::drawImage(const QRect &updateRect)
785{
786 if (!d->displayShader) {
787 return;
788 }
789
790 glEnable(GL_BLEND);
791 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
792
794
795 d->displayShader->bind();
796
797 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
798
799 QMatrix4x4 textureMatrix;
800 textureMatrix.setToIdentity();
801 d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix);
802
803 QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
804
805 if (!updateRect.isEmpty()) {
806 widgetRect &= updateRect;
807 }
808
809 QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect));
810
811 const QRect renderingLimit = canvas()->renderingLimit();
812
813 if (!renderingLimit.isEmpty()) {
814 widgetRectInImagePixels &= renderingLimit;
815 }
816
817 qreal scaleX, scaleY;
818 converter->imagePhysicalScale(&scaleX, &scaleY);
819
820 d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX);
822
824 QRect wr = widgetRectInImagePixels.toAlignedRect();
825
826 if (!d->wrapAroundMode) {
827 // if we don't want to paint wrapping images, just limit the
828 // processing area, and the code will handle all the rest
829 wr &= ir;
830 }
832 wr.setTop(ir.top());
833 wr.setBottom(ir.bottom());
834 }
836 wr.setLeft(ir.left());
837 wr.setRight(ir.right());
838 }
839
840 const int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir);
841 const int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir);
842 const int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir);
843 const int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir);
844
845 const int minColumn = d->openGLImageTextures->xToCol(ir.left());
846 const int maxColumn = d->openGLImageTextures->xToCol(ir.right());
847 const int minRow = d->openGLImageTextures->yToRow(ir.top());
848 const int maxRow = d->openGLImageTextures->yToRow(ir.bottom());
849
850 const int imageColumns = maxColumn - minColumn + 1;
851 const int imageRows = maxRow - minRow + 1;
852
853 if (d->displayFilter) {
854 d->displayFilter->setupTextures(this, d->displayShader);
855 }
856
857 const int firstCloneX = qFloor(qreal(firstColumn) / imageColumns);
858 const int lastCloneX = qFloor(qreal(lastColumn) / imageColumns);
859 const int firstCloneY = qFloor(qreal(firstRow) / imageRows);
860 const int lastCloneY = qFloor(qreal(lastRow) / imageRows);
861
862 for (int cloneY = firstCloneY; cloneY <= lastCloneY; cloneY++) {
863 for (int cloneX = firstCloneX; cloneX <= lastCloneX; cloneX++) {
864
865 const int localFirstCol = cloneX == firstCloneX ? KisAlgebra2D::wrapValue(firstColumn, imageColumns) : 0;
866 const int localLastCol = cloneX == lastCloneX ? KisAlgebra2D::wrapValue(lastColumn, imageColumns) : imageColumns - 1;
867
868 const int localFirstRow = cloneY == firstCloneY ? KisAlgebra2D::wrapValue(firstRow, imageRows) : 0;
869 const int localLastRow = cloneY == lastCloneY ? KisAlgebra2D::wrapValue(lastRow, imageRows) : imageRows - 1;
870
871 drawImageTiles(localFirstCol, localLastCol,
872 localFirstRow, localLastRow,
873 scaleX, scaleY, QPoint(cloneX, cloneY));
874 }
875 }
876
877 d->displayShader->release();
878
879 glDisable(GL_BLEND);
880}
881
882void KisOpenGLCanvasRenderer::drawImageTiles(int firstCol, int lastCol, int firstRow, int lastRow, qreal scaleX, qreal scaleY, const QPoint &wrapAroundOffset)
883{
885 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
886
887 QMatrix4x4 projectionMatrix;
888 projectionMatrix.setToIdentity();
889 // FIXME: It may be better to have the projection in device pixel, but
890 // this requires introducing a new coordinate system.
891 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
892
893 QTransform modelTransform = converter->imageToWidgetTransform();
894
895 if (!wrapAroundOffset.isNull()) {
896 const QRect ir = d->openGLImageTextures->storedImageBounds();
897
898 const QTransform wrapAroundTranslate = QTransform::fromTranslate(ir.width() * wrapAroundOffset.x(),
899 ir.height() * wrapAroundOffset.y());
900 modelTransform = wrapAroundTranslate * modelTransform;
901 }
902
903 // Set view/projection matrices
904 QMatrix4x4 modelMatrix(modelTransform);
905 modelMatrix.optimize();
906 modelMatrix = projectionMatrix * modelMatrix;
907 d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix);
908
909 int lastTileLodPlane = -1;
910
911 for (int col = firstCol; col <= lastCol; col++) {
912 for (int row = firstRow; row <= lastRow; row++) {
913
914 KisTextureTile *tile =
916
917 if (!tile) {
918 warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet.";
919 continue;
920 }
921
922 //Setup the geometry for rendering
924 const int tileIndex = d->openGLImageTextures->getTextureBufferIndexCR(col, row);
925
926 const int vertexRectSize = 6 * 3 * sizeof(float);
928 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * vertexRectSize));
929
930 const int textureRectSize = 6 * 2 * sizeof(float);
932 glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * textureRectSize));
933
934 } else {
935
936 const QRectF textureRect = tile->tileRectInTexturePixels();
937 const QRectF modelRect = tile->tileRectInImagePixels();
938
940 d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
941 d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
942
944 d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
946 }
947
948 glActiveTexture(GL_TEXTURE0);
949
950 // switching uniform is a rather expensive operation on macOS, so we change it only
951 // when it is really needed
952 const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress);
954 (lastTileLodPlane < 0 || lastTileLodPlane != currentLodPlane)) {
955
957 (GLfloat) currentLodPlane);
958 lastTileLodPlane = currentLodPlane;
959 }
960
961 if (currentLodPlane > 0) {
962 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
963 } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) {
964 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
965 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
966 } else {
967 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
968
969 switch(d->filterMode) {
971 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
972 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
973 break;
975 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
976 break;
978 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
979 break;
981 if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) {
982 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
983 } else {
984 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
985 }
986 break;
987 }
988 }
989
990 glDrawArrays(GL_TRIANGLES, 0, 6);
991 }
992 }
993
994 glBindTexture(GL_TEXTURE_2D, 0);
995 glBindBuffer(GL_ARRAY_BUFFER, 0);
996}
997
1010
1012{
1013 KisConfig cfg(true);
1014 bool useSeparateEraserCursor = cfg.separateEraserCursor() &&
1016
1017 d->cursorColor = (!useSeparateEraserCursor) ? cfg.getCursorMainColor() : cfg.getEraserCursorMainColor();
1018}
1019
1028
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
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
1050void KisOpenGLCanvasRenderer::renderCanvasGL(const QRect &updateRect)
1051{
1052 if ((d->displayFilter && d->displayFilter->updateShader()) ||
1054
1056
1057 d->canvasInitialized = false; // TODO: check if actually needed?
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
1099
1100void KisOpenGLCanvasRenderer::channelSelectionChanged(const QBitArray &channelFlags)
1101{
1102 d->openGLImageTextures->setChannelFlags(channelFlags);
1103}
1104
1105
1107{
1108 if (d->canvasInitialized) {
1110 }
1111}
1112
1114{
1115 if (canvas()->proofingConfigUpdated()) {
1116 d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
1118 }
1120}
1121
1122
1124{
1125 // See KisQPainterCanvas::updateCanvasProjection for more info
1126 bool isOpenGLUpdateInfo = dynamic_cast<KisOpenGLUpdateInfo*>(info.data());
1127 if (isOpenGLUpdateInfo) {
1129 }
1130
1131 const QRect dirty = kisGrowRect(coordinatesConverter()->imageToWidget(info->dirtyImageRect()).toAlignedRect(), 2);
1132 return dirty;
1133}
1134
float value(const T *src, size_t ch)
QPointF p2
QPointF p1
#define NEAR_VAL
static constexpr int NumberOfBuffers
#define FAR_VAL
WrapAroundAxis
@ WRAPAROUND_HORIZONTAL
@ WRAPAROUND_BOTH
@ WRAPAROUND_VERTICAL
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const QString COMPOSITE_ERASE
void startUpdateInPatches(const QRect &imageRect)
QRect renderingLimit
void setProofingConfigUpdated(bool updated)
setProofingConfigUpdated This function is to set whether the proofing config is updated,...
void updateCanvas(const QRectF &rc) override
static QImage createCheckersImage(qint32 checkSize=-1)
int openGLFilteringMode(bool defaultValue=false) const
void setCanvasState(const QString &state) const
qreal getPixelGridDrawingThreshold(bool defaultValue=false) const
int numMipmapLevels(bool defaultValue=false) const
void disableOpenGL() const
QColor getPixelGridColor(bool defaultValue=false) const
bool pixelGridEnabled(bool defaultValue=false) const
bool useOpenGLTextureBuffer(bool defaultValue=false) const
qint32 checkSize(bool defaultValue=false) const
bool scrollCheckers(bool defaultValue=false) const
QColor getCursorMainColor(bool defaultValue=false) const
bool separateEraserCursor(bool defaultValue=false) const
QColor getEraserCursorMainColor(bool defaultValue=false) const
_Private::Traits< T >::Result widgetToDocument(const T &obj) const
_Private::Traits< T >::Result widgetToViewport(const T &obj) const
void imagePhysicalScale(qreal *scaleX, qreal *scaleY) const
void getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const
_Private::Traits< T >::Result widgetToImage(const T &obj) const
_Private::Traits< T >::Result documentToImage(const T &obj) const
_Private::Traits< T >::Result imageToViewport(const T &obj) const
KisDisplayConfig This class keeps track of the color management configuration for image to display....
KoColorConversionTransformation::ConversionFlags conversionFlags
const KoColorProfile * profile
KoColorConversionTransformation::Intent intent
virtual KisCanvas2 * canvas() const =0
virtual QColor borderColor() const =0
virtual KisCoordinatesConverter * coordinatesConverter() const =0
virtual QOpenGLContext * openglContext() const =0
virtual GLenum internalTextureFormat() const =0
virtual qreal devicePixelRatioF() const =0
QRect updateCanvasProjection(KisUpdateInfoSP info)
void setDisplayFilter(QSharedPointer< KisDisplayFilter > displayFilter)
void drawImage(const QRect &updateRect)
void drawBackground(const QRect &updateRect)
QRectF surfaceToWidget(const QRectF &rc)
void resizeGL(int width, int height)
KisOpenGLImageTexturesSP openGLImageTextures() const
void channelSelectionChanged(const QBitArray &channelFlags)
QOpenGLContext * context() const
KisUpdateInfoSP startUpdateCanvasProjection(const QRect &rc)
void paintCanvasOnly(const QRect &canvasImageDirtyRect, const QRect &viewportUpdateRect=QRect())
void setDisplayFilterImpl(QSharedPointer< KisDisplayFilter > displayFilter, bool initializing)
QRectF widgetToSurface(const QRectF &rc)
void drawImageTiles(int firstCol, int lastCol, int firstRow, int lastRow, qreal scaleX, qreal scaleY, const QPoint &wrapAroundOffset)
void drawGrid(const QRect &updateRect)
void reportFailedShaderCompilation(const QString &context)
void setDisplayConfig(const KisDisplayConfig &config)
WrapAroundAxis wrapAroundViewingModeAxis() const
KisOpenGLCanvasRenderer(CanvasBridge *canvasBridge, KisImageWSP image, const KisDisplayConfig &displayConfig, QSharedPointer< KisDisplayFilter > displayFilter)
void setWrapAroundViewingModeAxis(WrapAroundAxis value)
void drawCheckers(const QRect &updateRect)
void finishResizingImage(qint32 w, qint32 h)
void paintToolOutline(const KisOptimizedBrushOutline &path, const QRect &viewportUpdateRect, const int thickness=1)
void renderCanvasGL(const QRect &updateRect)
KisCoordinatesConverter * coordinatesConverter() const
void notifyImageColorSpaceChanged(const KoColorSpace *cs)
bool setInternalColorManagementActive(bool value)
int getTextureBufferIndexCR(int col, int row)
KisOpenGLUpdateInfoSP updateCache(const QRect &rect, KisImageSP srcImage)
void generateCheckerTexture(const QImage &checkImage)
const KoColorProfile * monitorProfile()
void initGL(QOpenGLFunctions *f)
void slotImageSizeChanged(qint32 w, qint32 h)
static const int BACKGROUND_TEXTURE_CHECK_SIZE
void updateConfig(bool useBuffer, int NumMipmapLevels)
void recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration)
bool setImageColorSpace(const KoColorSpace *cs)
static KisOpenGLImageTexturesSP createImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
void setChannelFlags(const QBitArray &channelFlags)
KisTextureTile * getTextureTileCR(int col, int row)
KisOpenGLUpdateInfoBuilder & updateInfoBuilder()
void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
void setProofingConfig(KisProofingConfigurationSP)
KisShaderProgram * loadSolidColorShader()
KisShaderProgram * loadCheckerShader()
KisShaderProgram * loadDisplayShader(QSharedPointer< KisDisplayFilter > displayFilter, bool useHiQualityFiltering)
static bool useFBOForToolOutlineRendering()
supportsRenderToFBO
@ NearestFilterMode
Definition kis_opengl.h:34
@ HighQualityFiltering
Definition kis_opengl.h:37
@ BilinearFilterMode
Definition kis_opengl.h:35
@ TrilinearFilterMode
Definition kis_opengl.h:36
static void initializeContext(QOpenGLContext *ctx)
Initialize shared OpenGL context.
static bool supportsVAO()
int location(Uniform uniform)
QRect tileRectInImagePixels()
QRectF tileRectInTexturePixels()
int bindToActiveTexture(bool blockMipmapRegeneration)
QPointer< KoCanvasResourceProvider > resourceManager
virtual KoID colorDepthId() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
quint8 * data()
Definition KoColor.h:144
const KoColorSpace * colorSpace() const
return the current colorSpace
Definition KoColor.h:82
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define SCALE_LESS_THAN(scX, scY, value)
#define SCALE_MORE_OR_EQUAL_TO(scX, scY, value)
#define bounds(x, a, b)
#define warnUI
Definition kis_debug.h:94
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
#define PROGRAM_TEXCOORD_ATTRIBUTE
#define PROGRAM_VERTEX_ATTRIBUTE
@ ModelViewProjection
auto maxDimension(Size size) -> decltype(size.width())
T wrapValue(T value, T wrapBounds)
Point normalize(const Point &a)
qreal norm(const T &a)
PointTypeTraits< T >::value_type dotProduct(const T &a, const T &b)
void rectToTexCoords(QVector2D *texCoords, const QRectF &rc)
void rectToVertices(QVector3D *vertices, const QRectF &rc)
void allocate(int numBuffers, int bufferSize)
KisOpenGLBufferCircularStorage checkersVertexBuffer
QScopedPointer< QOpenGLFramebufferObject > canvasFBO
int yToRowWithWrapCompensation(int y, const QRect &imageRect)
int xToColWithWrapCompensation(int x, const QRect &imageRect)
KisOpenGLBufferCircularStorage checkersTextureVertexBuffer
QSharedPointer< KisDisplayFilter > displayFilter
const KoColorSpace * destinationColorSpace() const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()