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
9#define GL_GLEXT_PROTOTYPES
10
12
13#include "kis_algebra_2d.h"
15#include "canvas/kis_canvas2.h"
19#include "KisOpenGLModeProber.h"
21#include "kis_config.h"
22#include "kis_debug.h"
23
24#include <QPainter>
25#include <QPainterPath>
26#include <QOpenGLPaintDevice>
27#include <QPointF>
28#include <QTransform>
29#include <QThread>
30#include <QFile>
31#include <QOpenGLShaderProgram>
32#include <QOpenGLVertexArrayObject>
33#include <QOpenGLBuffer>
34#include <QOpenGLFramebufferObject>
35#include <QOpenGLFramebufferObjectFormat>
36#include <QMessageBox>
37#include <QVector3D>
41#include "kis_painting_tweaks.h"
43#include <KisDisplayConfig.h>
44
45#include <config-ocio.h>
46
47#define NEAR_VAL -1000.0
48#define FAR_VAL 1000.0
49
50#ifndef GL_CLAMP_TO_EDGE
51#define GL_CLAMP_TO_EDGE 0x812F
52#endif
53
54#define PROGRAM_VERTEX_ATTRIBUTE 0
55#define PROGRAM_TEXCOORD_ATTRIBUTE 1
56
57// These buffers are used only for painting checkers,
58// so we can keep the number really low
59static constexpr int NumberOfBuffers = 2;
60
62{
63public:
65 delete displayShader;
66 delete checkerShader;
67 delete solidColorShader;
68
69 delete canvasBridge;
70 }
71
72 bool canvasInitialized{false};
73
75
80
81 QScopedPointer<QOpenGLFramebufferObject> canvasFBO;
82
84
87
91
92 bool wrapAroundMode{false};
94
95 // Stores a quad for drawing the canvas
96 QOpenGLVertexArrayObject quadVAO;
97
100
101 // Stores data for drawing tool outlines
102 QOpenGLVertexArrayObject outlineVAO;
103 QOpenGLBuffer lineVertexBuffer;
105
106 QVector3D vertices[6];
107 QVector2D texCoords[6];
108
111 QColor gridColor;
113
115
119
120 int xToColWithWrapCompensation(int x, const QRect &imageRect) {
121 int firstImageColumn = openGLImageTextures->xToCol(imageRect.left());
122 int lastImageColumn = openGLImageTextures->xToCol(imageRect.right());
123
124 int colsPerImage = lastImageColumn - firstImageColumn + 1;
125 int numWraps = floor(qreal(x) / imageRect.width());
126 int remainder = x - imageRect.width() * numWraps;
127
128 return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder);
129 }
130
131 int yToRowWithWrapCompensation(int y, const QRect &imageRect) {
132 int firstImageRow = openGLImageTextures->yToRow(imageRect.top());
133 int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom());
134
135 int rowsPerImage = lastImageRow - firstImageRow + 1;
136 int numWraps = floor(qreal(y) / imageRect.height());
137 int remainder = y - imageRect.height() * numWraps;
138
139 return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder);
140 }
141
142};
143
145 KisImageWSP image,
146 const KisDisplayConfig &displayConfig,
148 : d(new Private())
149{
150 d->canvasBridge = canvasBridge;
151
152 const KisDisplayConfig &config = displayConfig;
153
156 config.profile,
157 config.intent,
158 config.conversionFlags);
159
160
161 setDisplayFilterImpl(displayFilter, true);
162}
163
168
173
174QOpenGLContext *KisOpenGLCanvasRenderer::context() const
175{
176 return d->canvasBridge->openglContext();
177}
178
183
188
190{
191 return d->canvasBridge->borderColor();
192}
193
198
200{
201 bool needsInternalColorManagement =
202 !displayFilter || displayFilter->useInternalColorManagement();
203
204 bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement, initializing);
205
206 d->displayFilter = displayFilter;
207
208 if (!initializing && needsFullRefresh) {
209 canvas()->startUpdateInPatches(canvas()->image()->bounds());
210 }
211 else if (!initializing) {
212 canvas()->updateCanvas();
213 }
214}
215
217{
218 // FIXME: on color space change the data is refetched multiple
219 // times by different actors!
220
222 canvas()->startUpdateInPatches(canvas()->image()->bounds());
223 }
224}
225
230
235
240
245
247{
249 initializeOpenGLFunctions();
250
251 KisConfig cfg(true);
252 d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
253 d->openGLImageTextures->initGL(context()->functions());
255
257
258 // If we support OpenGL 3.0, then prepare our VAOs and VBOs for drawing
260 d->quadVAO.create();
261 d->quadVAO.bind();
262
263 glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
264 glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE);
265
266 d->checkersVertexBuffer.allocate(NumberOfBuffers, 6 * 3 * sizeof(float));
267 d->checkersTextureVertexBuffer.allocate(NumberOfBuffers, 6 * 2 * sizeof(float));
268
269 // Create the outline buffer, this buffer will store the outlines of
270 // tools and will frequently change data
271 d->outlineVAO.create();
272 d->outlineVAO.bind();
273
274 glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
275
276 // The outline buffer has a StreamDraw usage pattern, because it changes constantly
277 d->lineVertexBuffer.create();
278 d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
279 d->lineVertexBuffer.bind();
280 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
281 }
282
283 d->canvasInitialized = true;
284}
285
307
309{
311
312 bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering;
313
314 delete d->displayShader;
315 d->displayShader = 0;
316
317 try {
318 d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering);
320 } catch (const ShaderLoaderException &e) {
322 }
323}
324
330{
331 KisConfig cfg(false);
332
333 qDebug() << "Shader Compilation Failure: " << context;
334 // TODO: Should do something else when using QtQuick2
335 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"),
336 i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context),
337 QMessageBox::Close);
338
339 cfg.disableOpenGL();
340 cfg.setCanvasState("OPENGL_FAILED");
341}
342
343void KisOpenGLCanvasRenderer::resizeGL(int width, int height)
344{
345 {
346 // just a sanity check!
347 //
348 // This is how QOpenGLCanvas sets the FBO and the viewport size. If
349 // devicePixelRatioF() is non-integral, the result is truncated.
350 // *Correction*: The FBO size is actually rounded, but the glViewport call
351 // uses integer truncation and that's what really matters.
352 int viewportWidth = static_cast<int>(width * devicePixelRatioF());
353 int viewportHeight = static_cast<int>(height * devicePixelRatioF());
354
355 // We expect the size to be adjusted in the converter at the higher
356 // level of hierarchy. It happens in KisCanvasControllerWidget, which
357 // then explicitly resizes the canvas widget.
358 KIS_SAFE_ASSERT_RECOVER_NOOP(QSize(viewportWidth, viewportHeight) == coordinatesConverter()->viewportDevicePixelSize());
359 }
360
363
365 QOpenGLFramebufferObjectFormat format;
366 format.setInternalTextureFormat(d->canvasBridge->internalTextureFormat());
367 d->canvasFBO.reset(new QOpenGLFramebufferObject(d->viewportDevicePixelSize, format));
368 }
369}
370
371void KisOpenGLCanvasRenderer::paintCanvasOnly(const QRect &canvasImageDirtyRect, const QRect &viewportUpdateRect)
372{
373 if (d->canvasFBO) {
374 if (!canvasImageDirtyRect.isEmpty()) {
375 d->canvasFBO->bind();
376 renderCanvasGL(canvasImageDirtyRect);
377 d->canvasFBO->release();
378 }
379 QRect blitRect;
380 if (viewportUpdateRect.isEmpty()) {
381 blitRect = QRect(QPoint(), d->viewportDevicePixelSize);
382 } else {
383 const QTransform scale = QTransform::fromScale(1.0, -1.0) * QTransform::fromTranslate(0, d->pixelAlignedWidgetSize.height()) * QTransform::fromScale(devicePixelRatioF(), devicePixelRatioF());
384 blitRect = scale.mapRect(QRectF(viewportUpdateRect)).toAlignedRect();
385 }
386 QOpenGLFramebufferObject::blitFramebuffer(nullptr, blitRect, d->canvasFBO.data(), blitRect, GL_COLOR_BUFFER_BIT, GL_NEAREST);
387 QOpenGLFramebufferObject::bindDefault();
388 } else {
389 QRect fullUpdateRect = canvasImageDirtyRect | viewportUpdateRect;
390 if (fullUpdateRect.isEmpty()) {
391 fullUpdateRect = QRect(QPoint(), d->viewportDevicePixelSize);
392 }
393 renderCanvasGL(fullUpdateRect);
394 }
395}
396
397void KisOpenGLCanvasRenderer::paintToolOutline(const KisOptimizedBrushOutline &path, const QRect &viewportUpdateRect, const int thickness)
398{
399 if (!d->solidColorShader->bind()) {
400 return;
401 }
402
403 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
404
405 // setup the mvp transformation
406 QMatrix4x4 projectionMatrix;
407 projectionMatrix.setToIdentity();
408 // FIXME: It may be better to have the projection in device pixel, but
409 // this requires introducing a new coordinate system.
410 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
411
412 // Set view/projection matrices
413 QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform());
414 modelMatrix.optimize();
415 modelMatrix = projectionMatrix * modelMatrix;
417
418 d->solidColorShader->setUniformValue(
420 QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f));
421
422 glEnable(GL_BLEND);
423 glBlendFuncSeparate(GL_ONE, GL_SRC_COLOR, GL_ONE, GL_ONE);
424 glBlendEquationSeparate(GL_FUNC_SUBTRACT, GL_FUNC_ADD);
425
426
427 if (!viewportUpdateRect.isEmpty()) {
428 const QRect deviceUpdateRect = widgetToSurface(viewportUpdateRect).toAlignedRect();
429 glScissor(deviceUpdateRect.x(), deviceUpdateRect.y(), deviceUpdateRect.width(), deviceUpdateRect.height());
430 glEnable(GL_SCISSOR_TEST);
431 }
432
433 // Paint the tool outline
435 d->outlineVAO.bind();
436 d->lineVertexBuffer.bind();
437 }
438
439
440 if (thickness > 1) {
441 // Because glLineWidth is not supported on all versions of OpenGL (or rather,
442 // is limited to 1, as returned by GL_ALIASED_LINE_WIDTH_RANGE),
443 // we'll instead generate mitered-triangles.
444
445 const qreal halfWidth = (thickness * 0.5) / devicePixelRatioF();
446 const qreal miterLimit = (5 * thickness) / devicePixelRatioF();
447
448 for (auto it = path.begin(); it != path.end(); ++it) {
449 const QPolygonF& polygon = *it;
450
451 if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5 * thickness) {
452 continue;
453 }
454
455 int triangleCount = 0;
457 d->lineVerticesStagingBuffer.reserve((polygon.count() - 1) * 6);
458 const bool closed = polygon.isClosed();
459
460 for( int i = 1; i < polygon.count(); i++) {
461 bool adjustFirst = closed? true: i > 1;
462 bool adjustSecond = closed? true: i + 1 < polygon.count();
463
464 QPointF p1 = polygon.at(i - 1);
465 QPointF p2 = polygon.at(i);
466 QPointF normal = p2 - p1;
467 normal = KisAlgebra2D::normalize(QPointF(-normal.y(), normal.x()));
468
469 QPointF c1 = p1 - (normal * halfWidth);
470 QPointF c2 = p1 + (normal * halfWidth);
471 QPointF c3 = p2 - (normal * halfWidth);
472 QPointF c4 = p2 + (normal * halfWidth);
473
474 // Add miter
475 if (adjustFirst) {
476 QPointF pPrev = i >= 2 ?
477 QPointF(polygon.at(i-2)) :
478 QPointF(polygon.at(qMax(polygon.count() - 2, 0)));
479
480 pPrev = p1 - pPrev;
481
482 QPointF miter =
485 QPointF(-pPrev.y(), pPrev.x())));
486
487 const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
488
489 const qreal dotMulti = dot != 0? 1.0/dot: 0.0;
490
491 if (KisAlgebra2D::norm((miter * halfWidth) * dotMulti) < miterLimit) {
492 c1 = p1 + ((miter * -halfWidth) * dotMulti);
493 c2 = p1 + ((miter * halfWidth) * dotMulti);
494 }
495 }
496
497 if (adjustSecond) {
498 QPointF pNext = i + 1 < polygon.count()? QPointF(polygon.at(i+1))
499 : QPointF(polygon.at(qMin(polygon.count(), 1)));
500 pNext = pNext - p2;
501 QPointF miter =
503 normal + KisAlgebra2D::normalize(QPointF(-pNext.y(), pNext.x())));
504 const qreal dot = KisAlgebra2D::dotProduct(miter, normal);
505 const qreal dotMulti = dot != 0? 1.0/dot: 0.0;
506
507 if (KisAlgebra2D::norm((miter * halfWidth) * dotMulti) < miterLimit) {
508 c3 = p2 + ((miter * -halfWidth) * dotMulti);
509 c4 = p2 + (miter * halfWidth) * dotMulti;
510 }
511 }
512
513 d->lineVerticesStagingBuffer.append(QVector3D(c1));
514 d->lineVerticesStagingBuffer.append(QVector3D(c3));
515 d->lineVerticesStagingBuffer.append(QVector3D(c2));
516 d->lineVerticesStagingBuffer.append(QVector3D(c4));
517 d->lineVerticesStagingBuffer.append(QVector3D(c2));
518 d->lineVerticesStagingBuffer.append(QVector3D(c3));
519 triangleCount += 2;
520 }
521
523 d->lineVertexBuffer.bind();
524 d->lineVertexBuffer.allocate(d->lineVerticesStagingBuffer.constData(), 3 * d->lineVerticesStagingBuffer.size() * sizeof(float));
525 }
526 else {
527 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
528 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->lineVerticesStagingBuffer.constData());
529 }
530
531 glDrawArrays(GL_TRIANGLES, 0, triangleCount * 3);
532 }
533 } else {
534 // Convert every disjointed subpath to a polygon and draw that polygon
535 for (auto it = path.begin(); it != path.end(); ++it) {
536 const QPolygonF& polygon = *it;
537
538 if (KisAlgebra2D::maxDimension(polygon.boundingRect()) < 0.5) {
539 continue;
540 }
541
542 const int verticesCount = polygon.count();
543
544 if (d->lineVerticesStagingBuffer.size() < verticesCount) {
545 d->lineVerticesStagingBuffer.resize(verticesCount);
546 }
547
548 for (int vertIndex = 0; vertIndex < verticesCount; vertIndex++) {
549 QPointF point = polygon.at(vertIndex);
550 d->lineVerticesStagingBuffer[vertIndex].setX(point.x());
551 d->lineVerticesStagingBuffer[vertIndex].setY(point.y());
552 }
554 d->lineVertexBuffer.bind();
555 d->lineVertexBuffer.allocate(d->lineVerticesStagingBuffer.constData(), 3 * verticesCount * sizeof(float));
556 }
557 else {
558 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
559 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->lineVerticesStagingBuffer.constData());
560 }
561
562
563
564 glDrawArrays(GL_LINE_STRIP, 0, verticesCount);
565 }
566 }
567
569 d->lineVertexBuffer.release();
570 d->outlineVAO.release();
571 }
572
573 if (!viewportUpdateRect.isEmpty()) {
574 glDisable(GL_SCISSOR_TEST);
575 }
576
577 glBlendEquation(GL_FUNC_ADD);
578 glBlendFunc(GL_ONE, GL_ZERO);
579 glDisable(GL_BLEND);
580
581 d->solidColorShader->release();
582}
583
588
589void KisOpenGLCanvasRenderer::drawBackground(const QRect &updateRect)
590{
591 Q_UNUSED(updateRect);
592
593 // Draw the border (that is, clear the whole widget to the border color)
594
595 QColor bgColor = colorToDisplaySpace(borderColor());
596 glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0);
597 glClear(GL_COLOR_BUFFER_BIT);
598}
599
600void KisOpenGLCanvasRenderer::drawCheckers(const QRect &updateRect)
601{
602 Q_UNUSED(updateRect);
603
604 if (!d->checkerShader) {
605 return;
606 }
607
609 QTransform textureTransform;
610 QTransform modelTransform;
611 QRectF textureRect;
612 QRectF modelRect;
613
614 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
615 QRectF viewportRect;
616 if (!d->wrapAroundMode) {
617 viewportRect = converter->imageRectInViewportPixels();
618 }
619 else {
620 const QRectF ir = converter->imageRectInViewportPixels();
621 viewportRect = converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height()));
623 viewportRect.setTop(ir.top());
624 viewportRect.setBottom(ir.bottom());
625 }
627 viewportRect.setLeft(ir.left());
628 viewportRect.setRight(ir.right());
629 }
630 }
631
632 // TODO: check if it works correctly
633 if (!canvas()->renderingLimit().isEmpty()) {
634 const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect();
635 viewportRect &= vrect;
636 }
637
638 converter->getOpenGLCheckersInfo(viewportRect,
639 &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers);
640
641 textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE,
643
644 if (!d->checkerShader->bind()) {
645 qWarning() << "Could not bind checker shader";
646 return;
647 }
648
649 QMatrix4x4 projectionMatrix;
650 projectionMatrix.setToIdentity();
651 // FIXME: It may be better to have the projection in device pixel, but
652 // this requires introducing a new coordinate system.
653 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
654
655 // Set view/projection matrices
656 QMatrix4x4 modelMatrix(modelTransform);
657 modelMatrix.optimize();
658 modelMatrix = projectionMatrix * modelMatrix;
659 d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix);
660
661 QMatrix4x4 textureMatrix(textureTransform);
662 d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix);
663
664 //Setup the geometry for rendering
667 QOpenGLBuffer *vertexBuf = d->checkersVertexBuffer.getNextBuffer();
668
669 vertexBuf->bind();
670 vertexBuf->write(0, d->vertices, 3 * 6 * sizeof(float));
671 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
672
673
675 QOpenGLBuffer *vertexTextureBuf = d->checkersTextureVertexBuffer.getNextBuffer();
676
677 vertexTextureBuf->bind();
678 vertexTextureBuf->write(0, d->texCoords, 2 * 6 * sizeof(float));
679 glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
680 }
681 else {
683 d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
684 d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
685
687 d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
689 }
690
691 // render checkers
692 glActiveTexture(GL_TEXTURE0);
693 glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture());
694
695 glDrawArrays(GL_TRIANGLES, 0, 6);
696
697 glBindTexture(GL_TEXTURE_2D, 0);
698 d->checkerShader->release();
699 glBindBuffer(GL_ARRAY_BUFFER, 0);
700}
701
702void KisOpenGLCanvasRenderer::drawGrid(const QRect &updateRect)
703{
704 if (!d->solidColorShader->bind()) {
705 return;
706 }
707
708 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
709
710 QMatrix4x4 projectionMatrix;
711 projectionMatrix.setToIdentity();
712 // FIXME: It may be better to have the projection in device pixel, but
713 // this requires introducing a new coordinate system.
714 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
715
716 // Set view/projection matrices
717 QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform());
718 modelMatrix.optimize();
719 modelMatrix = projectionMatrix * modelMatrix;
721
722 glEnable(GL_BLEND);
723 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
724
725 d->solidColorShader->setUniformValue(
727 QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f));
728
729 QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
730 QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect));
731 QRect wr = widgetRectInImagePixels.toAlignedRect();
732
733 if (!d->wrapAroundMode) {
735 }
736
737 if (!updateRect.isEmpty()) {
738 const QRect updateRectInImagePixels = coordinatesConverter()->widgetToImage(updateRect).toAlignedRect();
739 wr &= updateRectInImagePixels;
740 }
741
742 QPoint topLeftCorner = wr.topLeft();
743 QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1);
745
746 for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) {
747 grid.append(QVector3D(i, topLeftCorner.y(), 0));
748 grid.append(QVector3D(i, bottomRightCorner.y(), 0));
749 }
750 for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) {
751 grid.append(QVector3D(topLeftCorner.x(), i, 0));
752 grid.append(QVector3D(bottomRightCorner.x(), i, 0));
753 }
754
756 d->outlineVAO.bind();
757 d->lineVertexBuffer.bind();
758 d->lineVertexBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float));
759 }
760 else {
761 d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
762 d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData());
763 }
764
765 glDrawArrays(GL_LINES, 0, grid.size());
766
768 d->lineVertexBuffer.release();
769 d->outlineVAO.release();
770 }
771
772 d->solidColorShader->release();
773 glDisable(GL_BLEND);
774}
775
776void KisOpenGLCanvasRenderer::drawImage(const QRect &updateRect)
777{
778 if (!d->displayShader) {
779 return;
780 }
781
782 glEnable(GL_BLEND);
783 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
784
786
787 d->displayShader->bind();
788
789 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
790
791 QMatrix4x4 textureMatrix;
792 textureMatrix.setToIdentity();
793 d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix);
794
795 QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
796
797 if (!updateRect.isEmpty()) {
798 widgetRect &= updateRect;
799 }
800
801 QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect));
802
803 const QRect renderingLimit = canvas()->renderingLimit();
804
805 if (!renderingLimit.isEmpty()) {
806 widgetRectInImagePixels &= renderingLimit;
807 }
808
809 qreal scaleX, scaleY;
810 converter->imagePhysicalScale(&scaleX, &scaleY);
811
812 d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX);
814
816 QRect wr = widgetRectInImagePixels.toAlignedRect();
817
818 if (!d->wrapAroundMode) {
819 // if we don't want to paint wrapping images, just limit the
820 // processing area, and the code will handle all the rest
821 wr &= ir;
822 }
824 wr.setTop(ir.top());
825 wr.setBottom(ir.bottom());
826 }
828 wr.setLeft(ir.left());
829 wr.setRight(ir.right());
830 }
831
832 const int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir);
833 const int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir);
834 const int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir);
835 const int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir);
836
837 const int minColumn = d->openGLImageTextures->xToCol(ir.left());
838 const int maxColumn = d->openGLImageTextures->xToCol(ir.right());
839 const int minRow = d->openGLImageTextures->yToRow(ir.top());
840 const int maxRow = d->openGLImageTextures->yToRow(ir.bottom());
841
842 const int imageColumns = maxColumn - minColumn + 1;
843 const int imageRows = maxRow - minRow + 1;
844
845 if (d->displayFilter) {
846 d->displayFilter->setupTextures(this, d->displayShader);
847 }
848
849 const int firstCloneX = qFloor(qreal(firstColumn) / imageColumns);
850 const int lastCloneX = qFloor(qreal(lastColumn) / imageColumns);
851 const int firstCloneY = qFloor(qreal(firstRow) / imageRows);
852 const int lastCloneY = qFloor(qreal(lastRow) / imageRows);
853
854 for (int cloneY = firstCloneY; cloneY <= lastCloneY; cloneY++) {
855 for (int cloneX = firstCloneX; cloneX <= lastCloneX; cloneX++) {
856
857 const int localFirstCol = cloneX == firstCloneX ? KisAlgebra2D::wrapValue(firstColumn, imageColumns) : 0;
858 const int localLastCol = cloneX == lastCloneX ? KisAlgebra2D::wrapValue(lastColumn, imageColumns) : imageColumns - 1;
859
860 const int localFirstRow = cloneY == firstCloneY ? KisAlgebra2D::wrapValue(firstRow, imageRows) : 0;
861 const int localLastRow = cloneY == lastCloneY ? KisAlgebra2D::wrapValue(lastRow, imageRows) : imageRows - 1;
862
863 drawImageTiles(localFirstCol, localLastCol,
864 localFirstRow, localLastRow,
865 scaleX, scaleY, QPoint(cloneX, cloneY));
866 }
867 }
868
869 d->displayShader->release();
870
871 glDisable(GL_BLEND);
872}
873
874void KisOpenGLCanvasRenderer::drawImageTiles(int firstCol, int lastCol, int firstRow, int lastRow, qreal scaleX, qreal scaleY, const QPoint &wrapAroundOffset)
875{
877 const QSizeF &widgetSize = d->pixelAlignedWidgetSize;
878
879 QMatrix4x4 projectionMatrix;
880 projectionMatrix.setToIdentity();
881 // FIXME: It may be better to have the projection in device pixel, but
882 // this requires introducing a new coordinate system.
883 projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL);
884
885 QTransform modelTransform = converter->imageToWidgetTransform();
886
887 if (!wrapAroundOffset.isNull()) {
888 const QRect ir = d->openGLImageTextures->storedImageBounds();
889
890 const QTransform wrapAroundTranslate = QTransform::fromTranslate(ir.width() * wrapAroundOffset.x(),
891 ir.height() * wrapAroundOffset.y());
892 modelTransform = wrapAroundTranslate * modelTransform;
893 }
894
895 // Set view/projection matrices
896 QMatrix4x4 modelMatrix(modelTransform);
897 modelMatrix.optimize();
898 modelMatrix = projectionMatrix * modelMatrix;
899 d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix);
900
901 int lastTileLodPlane = -1;
902
903 for (int col = firstCol; col <= lastCol; col++) {
904 for (int row = firstRow; row <= lastRow; row++) {
905
906 KisTextureTile *tile =
908
909 if (!tile) {
910 warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet.";
911 continue;
912 }
913
914 //Setup the geometry for rendering
916 const int tileIndex = d->openGLImageTextures->getTextureBufferIndexCR(col, row);
917
918 const int vertexRectSize = 6 * 3 * sizeof(float);
920 glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * vertexRectSize));
921
922 const int textureRectSize = 6 * 2 * sizeof(float);
924 glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(tileIndex * textureRectSize));
925
926 } else {
927
928 const QRectF textureRect = tile->tileRectInTexturePixels();
929 const QRectF modelRect = tile->tileRectInImagePixels();
930
932 d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
933 d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
934
936 d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
938 }
939
940 glActiveTexture(GL_TEXTURE0);
941
942 // switching uniform is a rather expensive operation on macOS, so we change it only
943 // when it is really needed
944 const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress);
946 (lastTileLodPlane < 0 || lastTileLodPlane != currentLodPlane)) {
947
949 (GLfloat) currentLodPlane);
950 lastTileLodPlane = currentLodPlane;
951 }
952
953 if (currentLodPlane > 0) {
954 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
955 } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) {
956 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
957 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
958 } else {
959 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
960
961 switch(d->filterMode) {
963 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
964 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
965 break;
967 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
968 break;
970 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
971 break;
973 if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) {
974 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
975 } else {
976 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
977 }
978 break;
979 }
980 }
981
982 glDrawArrays(GL_TRIANGLES, 0, 6);
983 }
984 }
985
986 glBindTexture(GL_TEXTURE_2D, 0);
987 glBindBuffer(GL_ARRAY_BUFFER, 0);
988}
989
1003
1005{
1006 KisConfig cfg(true);
1007 bool useSeparateEraserCursor = cfg.separateEraserCursor() &&
1009
1010 d->cursorColor = colorToDisplaySpace((!useSeparateEraserCursor) ? cfg.getCursorMainColor() : cfg.getEraserCursorMainColor());
1011}
1012
1021
1023{
1024 const qreal ratio = devicePixelRatioF();
1025
1026 return QRectF(rc.x() * ratio,
1027 (d->pixelAlignedWidgetSize.height() - rc.y() - rc.height()) * ratio,
1028 rc.width() * ratio,
1029 rc.height() * ratio);
1030}
1031
1033{
1034 const qreal ratio = devicePixelRatioF();
1035
1036 return QRectF(rc.x() / ratio,
1037 d->pixelAlignedWidgetSize.height() - (rc.y() + rc.height()) / ratio,
1038 rc.width() / ratio,
1039 rc.height() / ratio);
1040}
1041
1042
1043void KisOpenGLCanvasRenderer::renderCanvasGL(const QRect &updateRect)
1044{
1045 if ((d->displayFilter && d->displayFilter->updateShader()) ||
1047
1049
1050 d->canvasInitialized = false; // TODO: check if actually needed?
1052 d->canvasInitialized = true;
1053 }
1054
1055 if (KisOpenGL::supportsVAO()) {
1056 d->quadVAO.bind();
1057 }
1058
1059 QRect alignedUpdateRect = updateRect;
1060
1061 if (!updateRect.isEmpty()) {
1062 const QRect deviceUpdateRect = widgetToSurface(updateRect).toAlignedRect();
1063 alignedUpdateRect = surfaceToWidget(deviceUpdateRect).toAlignedRect();
1064
1065 glScissor(deviceUpdateRect.x(), deviceUpdateRect.y(), deviceUpdateRect.width(), deviceUpdateRect.height());
1066 glEnable(GL_SCISSOR_TEST);
1067 }
1068
1069 drawBackground(alignedUpdateRect);
1070 drawCheckers(alignedUpdateRect);
1071 drawImage(alignedUpdateRect);
1072
1073 if ((coordinatesConverter()->effectivePhysicalZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) {
1074 drawGrid(alignedUpdateRect);
1075 }
1076
1077 if (!updateRect.isEmpty()) {
1078 glDisable(GL_SCISSOR_TEST);
1079 }
1080
1081 if (KisOpenGL::supportsVAO()) {
1082 d->quadVAO.release();
1083 }
1084}
1085
1087 KoColor convertedColor = KoColor(c, KoColorSpaceRegistry::instance()->rgb8());
1090}
1091
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 QString COMPOSITE_ERASE
void startUpdateInPatches(const QRect &imageRect)
QRect renderingLimit
KoColorDisplayRendererInterface * displayRendererInterface() const override
displayRendererInterface The display renderer interface has a number of color conversion functions wh...
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
QColor colorToDisplaySpace(const QColor &c)
void notifyImageColorSpaceChanged(const KoColorSpace *cs)
int getTextureBufferIndexCR(int col, int row)
KisOpenGLUpdateInfoSP updateCache(const QRect &rect, KisImageSP srcImage)
void generateCheckerTexture(const QImage &checkImage)
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()
bool setInternalColorManagementActive(bool value, bool iniializing=false)
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 QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
#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
static KoColorSpaceRegistry * instance()