diff --git a/krita/data/shaders/overlay_inverted.frag b/krita/data/shaders/overlay_inverted.frag new file mode 100644 index 0000000000..762c8b234c --- /dev/null +++ b/krita/data/shaders/overlay_inverted.frag @@ -0,0 +1,10 @@ +uniform vec4 fragColor; +uniform sampler2D texture0; + +in vec4 v_textureCoordinate; +out vec4 resultFragmentColor; + +void main(void) +{ + resultFragmentColor = fragColor * vec4(vec3(1.0 - texture(texture0, v_textureCoordinate.xy)), 1.0); +} diff --git a/krita/data/shaders/shaders.qrc b/krita/data/shaders/shaders.qrc index c3b076ac46..646a8a1c41 100644 --- a/krita/data/shaders/shaders.qrc +++ b/krita/data/shaders/shaders.qrc @@ -1,17 +1,18 @@ bilinear_gradient.frag conical_gradient.frag conical_symetric_gradient.frag highq_downscale.frag linear_gradient.frag matrix_transform.vert radial_gradient.frag simple_texture.frag square_gradient.frag matrix_transform_legacy.vert simple_texture_legacy.frag solid_color.frag solid_color_legacy.frag + overlay_inverted.frag diff --git a/libs/global/kis_algebra_2d.cpp b/libs/global/kis_algebra_2d.cpp index ac6383bec0..2bfb88edda 100644 --- a/libs/global/kis_algebra_2d.cpp +++ b/libs/global/kis_algebra_2d.cpp @@ -1,681 +1,681 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_algebra_2d.h" #include #include #include #include #include #include #include #include #include #include #include #define SANITY_CHECKS namespace KisAlgebra2D { void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt) { const int numPoints = poly.size(); for (int i = 0; i < numPoints; i++) { int nextI = i + 1; if (nextI >= numPoints) { nextI = 0; } const QPointF &p0 = poly[i]; const QPointF &p1 = poly[nextI]; QPointF edge = p1 - p0; qreal cross = crossProduct(edge, *pt - p0) / (0.5 * edge.manhattanLength()); if (cross < 1.0 && isInRange(pt->x(), p0.x(), p1.x()) && isInRange(pt->y(), p0.y(), p1.y())) { QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection); QPointF adjustedPoint = *pt + salt; // in case the polygon is self-intersecting, polygon direction // might not help if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { adjustedPoint = *pt - salt; #ifdef SANITY_CHECKS if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { dbgKrita << ppVar(*pt); dbgKrita << ppVar(adjustedPoint); dbgKrita << ppVar(QLineF(p0, p1)); dbgKrita << ppVar(salt); dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill)); dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1))); dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1))); } *pt = adjustedPoint; KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4); #endif /* SANITY_CHECKS */ } } } } QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) { qreal len1 = norm(base1); if (len1 < 1e-5) return pt; qreal sin1 = base1.y() / len1; qreal cos1 = base1.x() / len1; qreal len2 = norm(base2); if (len2 < 1e-5) return QPointF(); qreal sin2 = base2.y() / len2; qreal cos2 = base2.x() / len2; qreal sinD = sin2 * cos1 - cos2 * sin1; qreal cosD = cos1 * cos2 + sin1 * sin2; qreal scaleD = len2 / len1; QPointF result; result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD); result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD); return result; } qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2) { qreal a1 = std::atan2(v1.y(), v1.x()); qreal a2 = std::atan2(v2.y(), v2.x()); return a2 - a1; } qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle) { if (fuzzyPointCompare(p1, p2)) { return defaultAngle; } const QVector2D diff(p2 - p1); return std::atan2(diff.y(), diff.x()); } QPainterPath smallArrow() { QPainterPath p; p.moveTo(5, 2); p.lineTo(-3, 8); p.lineTo(-5, 5); p.lineTo( 2, 0); p.lineTo(-5,-5); p.lineTo(-3,-8); p.lineTo( 5,-2); p.arcTo(QRectF(3, -2, 4, 4), 90, -180); return p; } template inline Point ensureInRectImpl(Point pt, const Rect &bounds) { if (pt.x() > bounds.right()) { pt.rx() = bounds.right(); } else if (pt.x() < bounds.left()) { pt.rx() = bounds.left(); } if (pt.y() > bounds.bottom()) { pt.ry() = bounds.bottom(); } else if (pt.y() < bounds.top()) { pt.ry() = bounds.top(); } return pt; } QPoint ensureInRect(QPoint pt, const QRect &bounds) { return ensureInRectImpl(pt, bounds); } QPointF ensureInRect(QPointF pt, const QRectF &bounds) { return ensureInRectImpl(pt, bounds); } bool intersectLineRect(QLineF &line, const QRect rect) { QPointF pt1 = QPointF(), pt2 = QPointF(); QPointF tmp; if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { pt1 = tmp; } } if (line.intersect(QLineF(rect.topRight(), rect.bottomRight()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomLeft(), rect.topLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (pt1.isNull() || pt2.isNull()) return false; // Attempt to retain polarity of end points if ((line.x1() < line.x2()) != (pt1.x() > pt2.x()) || (line.y1() < line.y2()) != (pt1.y() > pt2.y())) { tmp = pt1; pt1 = pt2; pt2 = tmp; } line.setP1(pt1); line.setP2(pt2); return true; } template QVector sampleRectWithPoints(const Rect &rect) { QVector points; Point m1 = 0.5 * (rect.topLeft() + rect.topRight()); Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight()); points << rect.topLeft(); points << m1; points << rect.topRight(); points << 0.5 * (rect.topLeft() + rect.bottomLeft()); points << 0.5 * (m1 + m2); points << 0.5 * (rect.topRight() + rect.bottomRight()); points << rect.bottomLeft(); points << m2; points << rect.bottomRight(); return points; } QVector sampleRectWithPoints(const QRect &rect) { return sampleRectWithPoints(rect); } QVector sampleRectWithPoints(const QRectF &rect) { return sampleRectWithPoints(rect); } template Rect approximateRectFromPointsImpl(const QVector &points) { using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const Point &pt, points) { accX(pt.x()); accY(pt.y()); } Rect resultRect; if (alignPixels) { resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); } else { resultRect.setCoords(min(accX), min(accY), max(accX), max(accY)); } return resultRect; } QRect approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRectF approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRect approximateRectWithPointTransform(const QRect &rect, std::function func) { QVector points = sampleRectWithPoints(rect); using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const QPoint &pt, points) { QPointF dstPt = func(pt); accX(dstPt.x()); accY(dstPt.y()); } QRect resultRect; resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); return resultRect; } QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p) { QVector points; const QLineF cutLine = p.getLine(); points << rc.topLeft(); points << rc.topRight(); points << rc.bottomRight(); points << rc.bottomLeft(); QPointF p1 = points[3]; bool p1Valid = p.pos(p1) >= 0; QVector resultPoints; for (int i = 0; i < 4; i++) { const QPointF p2 = points[i]; const bool p2Valid = p.pos(p2) >= 0; if (p1Valid != p2Valid) { QPointF intersection; cutLine.intersect(QLineF(p1, p2), &intersection); resultPoints << intersection; } if (p2Valid) { resultPoints << p2; } p1 = p2; p1Valid = p2Valid; } return approximateRectFromPoints(resultPoints); } int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2) { int numSolutions = 0; const qreal D = pow2(b) - 4 * a * c; const qreal eps = 1e-14; if (qAbs(D) <= eps) { *x1 = -b / (2 * a); numSolutions = 1; } else if (D < 0) { return 0; } else { const qreal sqrt_D = std::sqrt(D); *x1 = (-b + sqrt_D) / (2 * a); *x2 = (-b - sqrt_D) / (2 * a); numSolutions = 2; } return numSolutions; } QVector intersectTwoCircles(const QPointF ¢er1, qreal r1, const QPointF ¢er2, qreal r2) { QVector points; const QPointF diff = (center2 - center1); const QPointF c1; const QPointF c2 = diff; const qreal centerDistance = norm(diff); if (centerDistance > r1 + r2) return points; if (centerDistance < qAbs(r1 - r2)) return points; if (centerDistance < qAbs(r1 - r2) + 0.001) { dbgKrita << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2)); return points; } const qreal x_kp1 = diff.x(); const qreal y_kp1 = diff.y(); const qreal F2 = 0.5 * (pow2(x_kp1) + pow2(y_kp1) + pow2(r1) - pow2(r2)); const qreal eps = 1e-6; if (qAbs(diff.y()) < eps) { qreal x = F2 / diff.x(); qreal y1, y2; int result = KisAlgebra2D::quadraticEquation( 1, 0, pow2(x) - pow2(r2), &y1, &y2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x, y1); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x, y1); QPointF p2(x, y2); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } else { const qreal A = diff.x() / diff.y(); const qreal C = F2 / diff.y(); qreal x1, x2; int result = KisAlgebra2D::quadraticEquation( 1 + pow2(A), -2 * A * C, pow2(C) - pow2(r1), &x1, &x2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x1, C - x1 * A); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x1, C - x1 * A); QPointF p2(x2, C - x2 * A); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } for (int i = 0; i < points.size(); i++) { points[i] = center1 + points[i]; } return points; } QTransform mapToRect(const QRectF &rect) { return QTransform(rect.width(), 0, 0, rect.height(), rect.x(), rect.y()); } QTransform mapToRectInverse(const QRectF &rect) { return QTransform::fromTranslate(-rect.x(), -rect.y()) * - QTransform::fromScale(rect.width() > 0 ? 1.0 / rect.width() : 0.0, - rect.height() > 0 ? 1.0 / rect.height() : 0.0); + QTransform::fromScale(rect.width() != 0 ? 1.0 / rect.width() : 0.0, + rect.height() != 0 ? 1.0 / rect.height() : 0.0); } bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) { return qAbs(t1.m11() - t2.m11()) < delta && qAbs(t1.m12() - t2.m12()) < delta && qAbs(t1.m13() - t2.m13()) < delta && qAbs(t1.m21() - t2.m21()) < delta && qAbs(t1.m22() - t2.m22()) < delta && qAbs(t1.m23() - t2.m23()) < delta && qAbs(t1.m31() - t2.m31()) < delta && qAbs(t1.m32() - t2.m32()) < delta && qAbs(t1.m33() - t2.m33()) < delta; } bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2) { return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y()); } bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta) { return qAbs(p1.x() - p2.x()) < delta && qAbs(p1.y() - p2.y()) < delta; } /********************************************************/ /* DecomposedMatix */ /********************************************************/ DecomposedMatix::DecomposedMatix() { } DecomposedMatix::DecomposedMatix(const QTransform &t0) { QTransform t(t0); QTransform projMatrix; if (t.m33() == 0.0 || t0.determinant() == 0.0) { qWarning() << "Cannot decompose matrix!" << t; valid = false; return; } if (t.type() == QTransform::TxProject) { QTransform affineTransform(t.toAffine()); projMatrix = affineTransform.inverted() * t; t = affineTransform; proj[0] = projMatrix.m13(); proj[1] = projMatrix.m23(); proj[2] = projMatrix.m33(); } std::array rows; rows[0] = QVector3D(t.m11(), t.m12(), t.m13()); rows[1] = QVector3D(t.m21(), t.m22(), t.m23()); rows[2] = QVector3D(t.m31(), t.m32(), t.m33()); if (!qFuzzyCompare(t.m33(), 1.0)) { const qreal invM33 = 1.0 / t.m33(); for (auto &row : rows) { row *= invM33; } } dx = rows[2].x(); dy = rows[2].y(); rows[2] = QVector3D(0,0,1); scaleX = rows[0].length(); rows[0] *= 1.0 / scaleX; shearXY = QVector3D::dotProduct(rows[0], rows[1]); rows[1] = rows[1] - shearXY * rows[0]; scaleY = rows[1].length(); rows[1] *= 1.0 / scaleY; shearXY *= 1.0 / scaleY; // If determinant is negative, one axis was flipped. qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x(); if (determinant < 0) { // Flip axis with minimum unit vector dot product. if (rows[0].x() < rows[1].y()) { scaleX = -scaleX; rows[0] = -rows[0]; } else { scaleY = -scaleY; rows[1] = -rows[1]; } shearXY = - shearXY; } angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x())); if (angle != 0.0) { // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] // = [row0x, -row0y, row0y, row0x] // Thanks to the normalization above. qreal sn = -rows[0].y(); qreal cs = rows[0].x(); qreal m11 = rows[0].x(); qreal m12 = rows[0].y(); qreal m21 = rows[1].x(); qreal m22 = rows[1].y(); rows[0].setX(cs * m11 + sn * m21); rows[0].setY(cs * m12 + sn * m22); rows[1].setX(-sn * m11 + cs * m21); rows[1].setY(-sn * m12 + cs * m22); } QTransform leftOver( rows[0].x(), rows[0].y(), rows[0].z(), rows[1].x(), rows[1].y(), rows[1].z(), rows[2].x(), rows[2].y(), rows[2].z()); KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)); } inline QTransform toQTransformStraight(const Eigen::Matrix3d &m) { return QTransform(m(0,0), m(0,1), m(0,2), m(1,0), m(1,1), m(1,2), m(2,0), m(2,1), m(2,2)); } inline Eigen::Matrix3d fromQTransformStraight(const QTransform &t) { Eigen::Matrix3d m; m << t.m11() , t.m12() , t.m13() ,t.m21() , t.m22() , t.m23() ,t.m31() , t.m32() , t.m33(); return m; } std::pair transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal) { KisAlgebra2D::DecomposedMatix decomposed(fullLocalToGlobal); const QTransform localToGlobal = decomposed.scaleTransform() * decomposed.shearTransform() * decomposed.rotateTransform(); const QTransform localEllipse = QTransform(1.0 / pow2(axes.x()), 0.0, 0.0, 0.0, 1.0 / pow2(axes.y()), 0.0, 0.0, 0.0, 1.0); const QTransform globalToLocal = localToGlobal.inverted(); Eigen::Matrix3d eqM = fromQTransformStraight(globalToLocal * localEllipse * globalToLocal.transposed()); // std::cout << "eqM:" << std::endl << eqM << std::endl; Eigen::EigenSolver eigenSolver(eqM); const Eigen::Matrix3d T = eigenSolver.eigenvalues().real().asDiagonal(); const Eigen::Matrix3d U = eigenSolver.eigenvectors().real(); const Eigen::Matrix3d Ti = eigenSolver.eigenvalues().imag().asDiagonal(); const Eigen::Matrix3d Ui = eigenSolver.eigenvectors().imag(); KIS_SAFE_ASSERT_RECOVER_NOOP(Ti.isZero()); KIS_SAFE_ASSERT_RECOVER_NOOP(Ui.isZero()); KIS_SAFE_ASSERT_RECOVER_NOOP((U * U.transpose()).isIdentity()); // std::cout << "T:" << std::endl << T << std::endl; // std::cout << "U:" << std::endl << U << std::endl; // std::cout << "Ti:" << std::endl << Ti << std::endl; // std::cout << "Ui:" << std::endl << Ui << std::endl; // std::cout << "UTU':" << std::endl << U * T * U.transpose() << std::endl; const qreal newA = 1.0 / std::sqrt(T(0,0) * T(2,2)); const qreal newB = 1.0 / std::sqrt(T(1,1) * T(2,2)); const QTransform newGlobalToLocal = toQTransformStraight(U); const QTransform newLocalToGlobal = QTransform::fromScale(-1,-1) * newGlobalToLocal.inverted() * decomposed.translateTransform(); return std::make_pair(QPointF(newA, newB), newLocalToGlobal); } QPointF alignForZoom(const QPointF &pt, qreal zoom) { return QPointF((pt * zoom).toPoint()) / zoom; } } diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp index 6589e67034..c47b106346 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.cpp +++ b/libs/ui/opengl/KisOpenGLModeProber.cpp @@ -1,333 +1,334 @@ /* * Copyright (c) 2017 Alvin Wong * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisOpenGLModeProber.h" #include #include #include #include #include #include Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance) KisOpenGLModeProber::KisOpenGLModeProber() { } KisOpenGLModeProber::~KisOpenGLModeProber() { } KisOpenGLModeProber *KisOpenGLModeProber::instance() { return s_instance; } bool KisOpenGLModeProber::useHDRMode() const { return isFormatHDR(QSurfaceFormat::defaultFormat()); } QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const { // TODO: use information provided by KisOpenGL instead QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext(); QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat(); return format; } const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace(); if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) { // use the default one! #ifdef HAVE_HDR } else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) { profile = KoColorSpaceRegistry::instance()->p709G10Profile(); } else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) { profile = KoColorSpaceRegistry::instance()->p2020PQProfile(); #endif } #endif return profile; } namespace { struct AppAttributeSetter { AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES) : m_attribute(attribute), m_oldValue(QCoreApplication::testAttribute(attribute)) { QCoreApplication::setAttribute(attribute, useOpenGLES); } ~AppAttributeSetter() { QCoreApplication::setAttribute(m_attribute, m_oldValue); } private: Qt::ApplicationAttribute m_attribute; bool m_oldValue = false; }; struct SurfaceFormatSetter { SurfaceFormatSetter(const QSurfaceFormat &format) : m_oldFormat(QSurfaceFormat::defaultFormat()) { QSurfaceFormat::setDefaultFormat(format); } ~SurfaceFormatSetter() { QSurfaceFormat::setDefaultFormat(m_oldFormat); } private: QSurfaceFormat m_oldFormat; }; #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) QString qEnvironmentVariable(const char *varName) { return qgetenv(varName); } #endif struct EnvironmentSetter { EnvironmentSetter(const QLatin1String &env, const QString &value) : m_env(env) { if (qEnvironmentVariableIsEmpty(m_env.latin1())) { m_oldValue = qgetenv(env.latin1()); } if (!value.isEmpty()) { qputenv(env.latin1(), value.toLatin1()); } else { qunsetenv(env.latin1()); } } ~EnvironmentSetter() { if (m_oldValue) { qputenv(m_env.latin1(), (*m_oldValue).toLatin1()); } else { qunsetenv(m_env.latin1()); } } private: const QLatin1String m_env; boost::optional m_oldValue; }; } boost::optional KisOpenGLModeProber::probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState) { const QSurfaceFormat &format = rendererConfig.format; QScopedPointer sharedContextSetter; QScopedPointer glSetter; QScopedPointer glesSetter; QScopedPointer formatSetter; QScopedPointer rendererSetter; QScopedPointer application; int argc = 1; QByteArray probeAppName("krita"); char *argv = probeAppName.data(); if (adjustGlobalState) { sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false)); if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) { glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES)); glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES)); } rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer))); formatSetter.reset(new SurfaceFormatSetter(format)); QGuiApplication::setDesktopSettingsAware(false); application.reset(new QGuiApplication(argc, &argv)); QGuiApplication::setDesktopSettingsAware(true); } QWindow surface; surface.setFormat(format); surface.setSurfaceType(QSurface::OpenGLSurface); surface.create(); QOpenGLContext context; context.setFormat(format); if (!context.create()) { dbgOpenGL << "OpenGL context cannot be created"; return boost::none; } if (!context.isValid()) { dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status"; return boost::none; } if (!context.makeCurrent(&surface)) { dbgOpenGL << "OpenGL context cannot be made current"; return boost::none; } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) { dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace(); return boost::none; } #endif return Result(context); } bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs) { return lhs == rhs || ((lhs == KisSurfaceColorSpace::DefaultColorSpace || lhs == KisSurfaceColorSpace::sRGBColorSpace) && (rhs == KisSurfaceColorSpace::DefaultColorSpace || rhs == KisSurfaceColorSpace::sRGBColorSpace)); } void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format) { #ifdef HAVE_HDR if (config == KisConfig::BT2020_PQ) { format->setRedBufferSize(10); format->setGreenBufferSize(10); format->setBlueBufferSize(10); format->setAlphaBufferSize(2); format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace); } else if (config == KisConfig::BT709_G10) { format->setRedBufferSize(16); format->setGreenBufferSize(16); format->setBlueBufferSize(16); format->setAlphaBufferSize(16); format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace); } else #else if (config == KisConfig::BT2020_PQ) { qWarning() << "WARNING: Bt.2020 PQ surface type is not supported by this build of Krita"; } else if (config == KisConfig::BT709_G10) { qWarning() << "WARNING: scRGB surface type is not supported by this build of Krita"; } #endif { format->setRedBufferSize(8); format->setGreenBufferSize(8); format->setBlueBufferSize(8); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) format->setAlphaBufferSize(8); #else format->setAlphaBufferSize(0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // TODO: check if we can use real sRGB space here format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace); #endif } } bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format) { #ifdef HAVE_HDR bool isBt2020PQ = format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace && format.redBufferSize() == 10 && format.greenBufferSize() == 10 && format.blueBufferSize() == 10 && format.alphaBufferSize() == 2; bool isBt709G10 = format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace && format.redBufferSize() == 16 && format.greenBufferSize() == 16 && format.blueBufferSize() == 16 && format.alphaBufferSize() == 16; return isBt2020PQ || isBt709G10; #else Q_UNUSED(format); return false; #endif } QString KisOpenGLModeProber::angleRendererToString(KisOpenGL::AngleRenderer renderer) { QString value; switch (renderer) { case KisOpenGL::AngleRendererDefault: break; case KisOpenGL::AngleRendererD3d9: value = "d3d9"; break; case KisOpenGL::AngleRendererD3d11: value = "d3d11"; break; case KisOpenGL::AngleRendererD3d11Warp: value = "warp"; break; }; return value; } KisOpenGLModeProber::Result::Result(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_vendorString = QString(reinterpret_cast(funcs->glGetString(GL_VENDOR))); m_shadingLanguageString = QString(reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); m_format = context.format(); + m_supportsFBO = context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.h b/libs/ui/opengl/KisOpenGLModeProber.h index d1aa59b30a..409aff25c9 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.h +++ b/libs/ui/opengl/KisOpenGLModeProber.h @@ -1,148 +1,153 @@ /* * Copyright (c) 2017 Alvin Wong * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISOPENGLMODEPROBER_H #define KISOPENGLMODEPROBER_H #include "kritaui_export.h" #include "kis_config.h" #include #include "KisSurfaceColorSpace.h" #include #include "kis_opengl.h" class KoColorProfile; class KRITAUI_EXPORT KisOpenGLModeProber { public: class Result; public: KisOpenGLModeProber(); ~KisOpenGLModeProber(); static KisOpenGLModeProber* instance(); bool useHDRMode() const; QSurfaceFormat surfaceformatInUse() const; const KoColorProfile *rootSurfaceColorProfile() const; boost::optional probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState = true); static bool fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs); static QString angleRendererToString(KisOpenGL::AngleRenderer renderer); public: static void initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format); static bool isFormatHDR(const QSurfaceFormat &format); }; class KisOpenGLModeProber::Result { public: Result(QOpenGLContext &context); int glMajorVersion() const { return m_glMajorVersion; } int glMinorVersion() const { return m_glMinorVersion; } bool supportsDeprecatedFunctions() const { return m_supportsDeprecatedFunctions; } bool isOpenGLES() const { return m_isOpenGLES; } QString rendererString() const { return m_rendererString; } QString driverVersionString() const { return m_driverVersionString; } bool isSupportedVersion() const { return #ifdef Q_OS_MACOS ((m_glMajorVersion * 100 + m_glMinorVersion) >= 302) #else (m_glMajorVersion >= 3 && (m_supportsDeprecatedFunctions || m_isOpenGLES)) || ((m_glMajorVersion * 100 + m_glMinorVersion) == 201) #endif ; } bool supportsLoD() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 300; } bool hasOpenGL3() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 302; } bool supportsFenceSync() const { return m_glMajorVersion >= 3; } + bool supportsFBO() const { + return m_supportsFBO; + } + #ifdef Q_OS_WIN // This is only for detecting whether ANGLE is being used. // For detecting generic OpenGL ES please check isOpenGLES bool isUsingAngle() const { return m_rendererString.startsWith("ANGLE", Qt::CaseInsensitive); } #endif QString shadingLanguageString() const { return m_shadingLanguageString; } QString vendorString() const { return m_vendorString; } QSurfaceFormat format() const { return m_format; } private: int m_glMajorVersion = 0; int m_glMinorVersion = 0; bool m_supportsDeprecatedFunctions = false; bool m_isOpenGLES = false; + bool m_supportsFBO = false; QString m_rendererString; QString m_driverVersionString; QString m_vendorString; QString m_shadingLanguageString; QSurfaceFormat m_format; }; #endif // KISOPENGLMODEPROBER_H diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index de2c4a0918..48211adb27 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,915 +1,921 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; if (info.isOpenGLES()) { const QString rendererString = info.rendererString().toLower(); if (rendererString.contains("basic render driver") || rendererString.contains("software")) { result = KisOpenGL::RendererSoftware; } else { result = KisOpenGL::RendererOpenGLES; } } return result; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); KisOpenGL::RendererConfig config; config.format = QSurfaceFormat::defaultFormat(); openGLCheckResult = KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; if (openGLCheckResult) { debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << openGLCheckResult->format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif // debugOut << "\n== log ==\n"; // debugOut.noquote(); // debugOut << g_surfaceFormatDetectionLog; // debugOut.resetFormat(); // debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; KisUsageLogger::writeSysInfo(g_debugText); if (!openGLCheckResult) { return; } // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif KisConfig cfg(true); if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { g_needsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) { g_needsPixmapCacheWorkaround = true; const QRect screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); } const QString &KisOpenGL::getDebugText() { initialize(); return g_debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return openGLCheckResult && openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); return openGLCheckResult && openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); return openGLCheckResult && openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); return openGLCheckResult && openGLCheckResult->supportsFenceSync(); } +bool KisOpenGL::supportsRenderToFBO() +{ + initialize(); + return openGLCheckResult && openGLCheckResult->supportsFBO(); +} + bool KisOpenGL::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { if (!openGLCheckResult) return RendererAuto; return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { case RendererNone: return QStringLiteral("none"); case RendererSoftware: return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; } else if (renderer == "software") { return RendererSoftware; } else if (renderer == "none") { return RendererNone; } else { return RendererAuto; } } KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const { KisOpenGL::OpenGLRenderer result = RendererAuto; if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11Warp) { result = RendererSoftware; } else if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11) { result = RendererOpenGLES; } else if (format.renderableType() == QSurfaceFormat::OpenGL) { result = RendererDesktopGL; } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && angleRenderer == AngleRendererD3d11) { // noop } else { qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); } return result; } namespace { typedef std::pair RendererInfo; RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { RendererInfo info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererD3d11}; switch (renderer) { case KisOpenGL::RendererNone: info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; break; case KisOpenGL::RendererAuto: break; case KisOpenGL::RendererDesktopGL: info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererOpenGLES: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererSoftware: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; break; } return info; } KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, KisConfig::RootSurfaceFormat rootSurfaceFormat, bool debugContext) { RendererInfo info = getRendererInfo(renderer); KisOpenGL::RendererConfig config; config.angleRenderer = info.second; QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #elif !defined(Q_OS_ANDROID) // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), isPreferredColorSpace(rhs.format.colorSpace())); #endif if (doPreferHDR()) { ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, rhs.rendererId() == m_preferredRendererByUser); } ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && m_preferredRendererByHDR != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, rhs.rendererId() == m_preferredRendererByHDR); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { return r == KisOpenGL::RendererSoftware; } bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || r == KisOpenGL::RendererDesktopGL || r == KisOpenGL::RendererOpenGLES || r == KisOpenGL::RendererSoftware || r == KisOpenGL::RendererNone); return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug) { QVector warningMessages; using Info = boost::optional; QHash renderersToTest; #ifndef Q_OS_ANDROID renderersToTest.insert(RendererDesktopGL, Info()); #endif renderersToTest.insert(RendererOpenGLES, Info()); #ifdef Q_OS_WIN renderersToTest.insert(RendererSoftware, Info()); #endif #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false); Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); #ifdef Q_OS_WIN if (!info) { // try software rasterizer (WARP) defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, KisConfig::BT709_G22, false); info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); if (!info) { renderersToTest.remove(RendererSoftware); } } #endif if (!info) return KisOpenGL::RendererConfig(); const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; compareOp.setPreferredRendererByQt(defaultRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm renderersToTest[defaultRenderer] = info; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { Info info = it.value(); if (!info) { info = KisOpenGLModeProber::instance()-> probeFormat(generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false)); *it = info; } compareOp.setOpenGLBlacklisted( !info || isOpenGLRendererBlacklisted(info->rendererString(), info->driverVersionString(), &warningMessages)); if (info && info->isSupportedVersion()) { supportedRenderers |= it.key(); } } OpenGLRenderer preferredByQt = defaultRenderer; if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } QVector preferredConfigs; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { // if default mode of the renderer doesn't work, then custom won't either if (!it.value()) continue; Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); } } std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { dbgDetection() << "*" << config.format; dbgDetection() << " " << config.rendererId(); } KisOpenGL::RendererConfig resultConfig = defaultConfig; if (preferredRenderer != RendererNone) { Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else dbgDetection() <<"Probing format..." << config.rendererId(); #endif Info info = KisOpenGLModeProber::instance()->probeFormat(config); if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN // HACK: Block ANGLE with Direct3D9 // Direct3D9 does not give OpenGL ES 3.0 // Some versions of ANGLE returns OpenGL version 3.0 incorrectly if (info->isUsingAngle() && info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; continue; } #endif dbgDetection() << "Found format:" << config.format; dbgDetection() << " " << config.rendererId(); resultConfig = config; break; } } { const bool colorSpaceIsCorrect = #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), resultConfig.format.colorSpace()); #else true; #endif const bool rendererIsCorrect = compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || compareOp.preferredRendererByUser() == resultConfig.rendererId(); if (!rendererIsCorrect && colorSpaceIsCorrect) { warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); } else if (!colorSpaceIsCorrect) { warningMessages << ki18n("Preferred output format is not supported by available renderers"); } } } else { resultConfig.format = QSurfaceFormat(); resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); return resultConfig; } void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; QSurfaceFormat::setDefaultFormat(config.format); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); #endif if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/opengl/kis_opengl.h b/libs/ui/opengl/kis_opengl.h index 365003a93b..4300de3696 100644 --- a/libs/ui/opengl/kis_opengl.h +++ b/libs/ui/opengl/kis_opengl.h @@ -1,137 +1,146 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_OPENGL_H_ #define KIS_OPENGL_H_ /** @file */ #include #include #include #include "kis_config.h" #include "kritaui_export.h" class QOpenGLContext; class QString; class QStringList; class QSurfaceFormat; /** * This class manages a shared OpenGL context and provides utility * functions for checking capabilities and error reporting. */ class KRITAUI_EXPORT KisOpenGL { public: enum FilterMode { NearestFilterMode, // nearest BilinearFilterMode, // linear, no mipmap TrilinearFilterMode, // LINEAR_MIPMAP_LINEAR HighQualityFiltering // Mipmaps + custom shader }; enum OpenGLRenderer { RendererNone = 0x00, RendererAuto = 0x01, RendererDesktopGL = 0x02, RendererOpenGLES = 0x04, RendererSoftware = 0x08 }; Q_DECLARE_FLAGS(OpenGLRenderers, OpenGLRenderer) enum AngleRenderer { AngleRendererDefault = 0x0000, AngleRendererD3d11 = 0x0002, AngleRendererD3d9 = 0x0004, AngleRendererD3d11Warp = 0x0008, // "Windows Advanced Rasterization Platform" }; struct RendererConfig { QSurfaceFormat format; AngleRenderer angleRenderer = AngleRendererDefault; OpenGLRenderer rendererId() const; }; public: static RendererConfig selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug); static void setDefaultSurfaceConfig(const RendererConfig &config); static OpenGLRenderer getCurrentOpenGLRenderer(); static OpenGLRenderer getQtPreferredOpenGLRenderer(); static OpenGLRenderers getSupportedOpenGLRenderers(); static OpenGLRenderer getUserPreferredOpenGLRendererConfig(); static void setUserPreferredOpenGLRendererConfig(OpenGLRenderer renderer); static QString convertOpenGLRendererToConfig(OpenGLRenderer renderer); static OpenGLRenderer convertConfigToOpenGLRenderer(QString renderer); /// Request OpenGL version 3.2 static void initialize(); /// Initialize shared OpenGL context static void initializeContext(QOpenGLContext *ctx); static const QString &getDebugText(); static QStringList getOpenGLWarnings(); static bool supportsLoD(); static bool hasOpenGL3(); static bool hasOpenGLES(); /// Check for OpenGL static bool hasOpenGL(); /** * @brief supportsFilter * @return True if OpenGL provides fence sync methods. */ static bool supportsFenceSync(); + /** + * @brief supportsRenderToFBO + * @return True if OpenGL can render to FBO, used + * currently for rendering cursor with image overlay + * fx. + */ + static bool supportsRenderToFBO(); + + /** * Returns true if we have a driver that has bugged support to sync objects (a fence) * and false otherwise. */ static bool needsFenceWorkaround(); /** * @see a comment in initializeContext() */ static bool needsPixmapCacheWorkaround(); static void testingInitializeDefaultSurfaceFormat(); static void setDebugSynchronous(bool value); private: static void fakeInitWindowsOpenGL(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt); KisOpenGL(); }; #ifdef Q_OS_WIN Q_DECLARE_OPERATORS_FOR_FLAGS(KisOpenGL::OpenGLRenderers); #endif #endif // KIS_OPENGL_H_ diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 59f2181f07..dce0f530b5 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1062 +1,1119 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" +#include "kis_algebra_2d.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include +#include #include #include #include #include #include #include #include +#include #include #include "KisOpenGLModeProber.h" #include #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) #include #endif #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; + delete overlayInvertedShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; + KisShaderProgram *overlayInvertedShader{0}; + + QScopedPointer canvasFBO; + bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; - QOpenGLBuffer lineBuffer; + QOpenGLBuffer lineVertexBuffer; + QOpenGLBuffer lineTexCoordBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); connect(d->openGLImageTextures.data(), SIGNAL(sigShowFloatingMessage(QString, int, bool)), SLOT(slotShowFloatingMessage(QString, int, bool))); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_MACOS setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg(true); d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); + glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly - d->lineBuffer.create(); - d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); - d->lineBuffer.bind(); + d->lineVertexBuffer.create(); + d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + d->lineVertexBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); + + d->lineTexCoordBuffer.create(); + d->lineTexCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + d->lineTexCoordBuffer.bind(); + glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0 ,0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; + delete d->overlayInvertedShader; d->checkerShader = 0; d->solidColorShader = 0; + d->overlayInvertedShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); + d->overlayInvertedShader = d->shaderLoader.loadOverlayInvertedShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); cfg.disableOpenGL(); cfg.setCanvasState("OPENGL_FAILED"); } -void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/) +void KisOpenGLCanvas2::resizeGL(int width, int height) { // The given size is the widget size but here we actually want to give // KisCoordinatesConverter the viewport size aligned to device pixels. + if (KisOpenGL::supportsRenderToFBO()) { + d->canvasFBO.reset(new QOpenGLFramebufferObject(QSize(width, height))); + } coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); + if (d->canvasFBO) { + d->canvasFBO->bind(); + } + renderCanvasGL(); + if (d->canvasFBO) { + d->canvasFBO->release(); + QOpenGLFramebufferObject::blitFramebuffer(nullptr, d->canvasFBO.data(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + QOpenGLFramebufferObject::bindDefault(); + } + if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { - if (!d->solidColorShader->bind()) { + if (!d->overlayInvertedShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); - // Set view/projection matrices + // Set view/projection & texture matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; - d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); + d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::ModelViewProjection), modelMatrix); - if (!KisOpenGL::hasOpenGLES()) { -#ifndef HAS_ONLY_OPENGL_ES - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + d->overlayInvertedShader->setUniformValue( + d->overlayInvertedShader->location(Uniform::FragmentColor), + QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); + + // NOTE: Texture matrix transforms flake space -> widget space -> OpenGL UV texcoord space.. + const QMatrix4x4 widgetToFBOTexCoordTransform = KisAlgebra2D::mapToRectInverse(QRect(QPoint(0, this->height()), + QSize(this->width(), -1 * this->height()))); + const QMatrix4x4 textureMatrix = widgetToFBOTexCoordTransform * + QMatrix4x4(coordinatesConverter()->flakeToWidgetTransform()); + d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::TextureMatrix), textureMatrix); + + // For the legacy shader, we should use old fixed function + // blending operations if available. + if (!KisOpenGL::hasOpenGL3() && !KisOpenGL::hasOpenGLES()) { + #ifndef HAS_ONLY_OPENGL_ES + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); -#ifndef Q_OS_MACOS + + #ifndef Q_OS_MACOS if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } -#else + #else // Q_OS_MACOS glLogicOp(GL_XOR); -#endif // Q_OS_OSX + #endif // Q_OS_MACOS -#else // HAS_ONLY_OPENGL_ES + #else // HAS_ONLY_OPENGL_ES KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", - "Unexpected KisOpenGL::hasOpenGLES returned false"); -#endif // HAS_ONLY_OPENGL_ES - } else { - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE); + "Unexpected KisOpenGL::hasOpenGLES returned false"); + #endif // HAS_ONLY_OPENGL_ES } - d->solidColorShader->setUniformValue( - d->solidColorShader->location(Uniform::FragmentColor), - QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); - // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); - d->lineBuffer.bind(); + d->lineVertexBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); - for (int i = 0; i < subPathPolygons.size(); i++) { - const QPolygonF& polygon = subPathPolygons.at(i); + for (int polyIndex = 0; polyIndex < subPathPolygons.size(); polyIndex++) { + const QPolygonF& polygon = subPathPolygons.at(polyIndex); QVector vertices; + QVector texCoords; vertices.resize(polygon.count()); - - for (int j = 0; j < polygon.count(); j++) { - QPointF p = polygon.at(j); - vertices[j].setX(p.x()); - vertices[j].setY(p.y()); + texCoords.resize(polygon.count()); + + for (int vertIndex = 0; vertIndex < polygon.count(); vertIndex++) { + QPointF point = polygon.at(vertIndex); + vertices[vertIndex].setX(point.x()); + vertices[vertIndex].setY(point.y()); + texCoords[vertIndex].setX(point.x()); + texCoords[vertIndex].setY(point.y()); } if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); + d->lineVertexBuffer.bind(); + d->lineVertexBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); + d->lineTexCoordBuffer.bind(); + d->lineTexCoordBuffer.allocate(texCoords.constData(), 2 * texCoords.size() * sizeof(float)); } else { - d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); - d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); + d->overlayInvertedShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); + d->overlayInvertedShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); + d->overlayInvertedShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); + d->overlayInvertedShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, texCoords.constData()); } - glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + const bool usingLegacyShader = !((KisOpenGL::hasOpenGL3() || KisOpenGL::hasOpenGLES()) && KisOpenGL::supportsRenderToFBO()); + if (usingLegacyShader){ + glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, d->canvasFBO->texture()); + + glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + + glBindTexture(GL_TEXTURE_2D, 0); + } } if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.release(); + d->lineVertexBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glDisable(GL_COLOR_LOGIC_OP); #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { glDisable(GL_BLEND); } - d->solidColorShader->release(); + d->overlayInvertedShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); - d->lineBuffer.bind(); + d->lineVertexBuffer.bind(); } QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); + d->lineVertexBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.release(); + d->lineVertexBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisable(GL_BLEND); } QSize KisOpenGLCanvas2::viewportDevicePixelSize() const { // This is how QOpenGLCanvas sets the FBO and the viewport size. If // devicePixelRatioF() is non-integral, the result is truncated. int viewportWidth = static_cast(width() * devicePixelRatioF()); int viewportHeight = static_cast(height() * devicePixelRatioF()); return QSize(viewportWidth, viewportHeight); } QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const { QSize viewportSize = viewportDevicePixelSize(); qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); return QSizeF(scaledWidth, scaledHeight); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } void KisOpenGLCanvas2::slotShowFloatingMessage(const QString &message, int timeout, bool priority) { canvas()->imageView()->showFloatingMessage(message, QIcon(), timeout, priority ? KisFloatingMessage::High : KisFloatingMessage::Medium); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); const KoColorSpace *finalColorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), d->openGLImageTextures->updateInfoBuilder().destinationColorSpace()->colorDepthId().id(), d->openGLImageTextures->monitorProfile()); KoColor convertedBackgroudColor = KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()); convertedBackgroudColor.convertTo(finalColorSpace); QVector channels = QVector(4); convertedBackgroudColor.colorSpace()->normalisedChannelsValue(convertedBackgroudColor.data(), channels); // Data returned by KoRgbU8ColorSpace comes in the order: blue, green, red. glClearColor(channels[2], channels[1], channels[0], 1.0); } glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_MACOS /** * On OSX openGL different (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_MACOS if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h index fc08867b5f..de46a89ce0 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -1,135 +1,136 @@ /* * Copyright (C) Boudewijn Rempt , (C) 2006 * Copyright (C) Michael Abrahams , (C) 2015 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_OPENGL_CANVAS_2_H #define KIS_OPENGL_CANVAS_2_H #include #ifndef Q_OS_MACOS #include #else #include #endif #include "canvas/kis_canvas_widget_base.h" #include "opengl/kis_opengl_image_textures.h" #include "kritaui_export.h" #include "kis_ui_types.h" class KisCanvas2; class KisDisplayColorConverter; class QOpenGLShaderProgram; class QPainterPath; #ifndef Q_MOC_RUN #ifndef Q_OS_MACOS #define GLFunctions QOpenGLFunctions #else #define GLFunctions QOpenGLFunctions_3_2_Core #endif #endif /** * KisOpenGLCanvas is the widget that shows the actual image using OpenGL * * NOTE: if you change something in the event handling here, also change it * in the qpainter canvas. * */ class KRITAUI_EXPORT KisOpenGLCanvas2 : public QOpenGLWidget #ifndef Q_MOC_RUN , protected GLFunctions #endif , public KisCanvasWidgetBase { Q_OBJECT public: KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter); ~KisOpenGLCanvas2() override; public: // QOpenGLWidget void resizeGL(int width, int height) override; void initializeGL() override; void paintGL() override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; void inputMethodEvent(QInputMethodEvent *event) override; public: void renderCanvasGL(); void renderDecorations(QPainter *painter); void paintToolOutline(const QPainterPath &path); + public: // Implement kis_abstract_canvas_widget interface void setDisplayFilter(QSharedPointer displayFilter) override; void notifyImageColorSpaceChanged(const KoColorSpace *cs) override; void setWrapAroundViewingMode(bool value) override; void channelSelectionChanged(const QBitArray &channelFlags) override; void setDisplayColorConverter(KisDisplayColorConverter *colorConverter) override; void finishResizingImage(qint32 w, qint32 h) override; KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override; QRect updateCanvasProjection(KisUpdateInfoSP info) override; QVector updateCanvasProjection(const QVector &infoObjects) override; QWidget *widget() override { return this; } bool isBusy() const override; void setLodResetInProgress(bool value) override; void setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing); KisOpenGLImageTexturesSP openGLImageTextures() const; public Q_SLOTS: void slotConfigChanged(); void slotPixelGridModeChanged(); private Q_SLOTS: void slotShowFloatingMessage(const QString &message, int timeout, bool priority); protected: // KisCanvasWidgetBase bool callFocusNextPrevChild(bool next) override; private: void initializeShaders(); void initializeDisplayShader(); void reportFailedShaderCompilation(const QString &context); void drawImage(); void drawCheckers(); void drawGrid(); QSize viewportDevicePixelSize() const; QSizeF widgetSizeAlignedToDevicePixel() const; private: struct Private; Private * const d; }; #endif // KIS_OPENGL_CANVAS_2_H diff --git a/libs/ui/opengl/kis_opengl_shader_loader.cpp b/libs/ui/opengl/kis_opengl_shader_loader.cpp index 306e3a56c0..574c0626b3 100644 --- a/libs/ui/opengl/kis_opengl_shader_loader.cpp +++ b/libs/ui/opengl/kis_opengl_shader_loader.cpp @@ -1,211 +1,229 @@ /* This file is part of the KDE project * Copyright (C) Julian Thijssen , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_opengl_shader_loader.h" #include "opengl/kis_opengl.h" #include "kis_config.h" #include #include #include #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 // Mapping of uniforms to uniform names std::map KisShaderProgram::names = { {ModelViewProjection, "modelViewProjection"}, {TextureMatrix, "textureMatrix"}, {ViewportScale, "viewportScale"}, {TexelSize, "texelSize"}, {Texture0, "texture0"}, {Texture1, "texture1"}, {FixedLodLevel, "fixedLodLevel"}, {FragmentColor, "fragColor"} }; /** * Generic shader loading function that will compile a shader program given * a vertex shader and fragment shader resource path. Extra code can be prepended * to each shader respectively using the header parameters. * * @param vertPath Resource path to a vertex shader * @param fragPath Resource path to a fragment shader * @param vertHeader Extra code which will be prepended to the vertex shader * @param fragHeader Extra code which will be prepended to the fragment shader */ KisShaderProgram *KisOpenGLShaderLoader::loadShader(QString vertPath, QString fragPath, QByteArray vertHeader, QByteArray fragHeader) { bool result; KisShaderProgram *shader = new KisShaderProgram(); // Load vertex shader QByteArray vertSource; // XXX Check can be removed and set to the MAC version after we move to Qt5.7 #ifdef Q_OS_MACOS vertSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n"); // OpenColorIO doesn't support the new GLSL version yet. vertSource.append("#define texture2D texture\n"); vertSource.append("#define texture3D texture\n"); #else if (KisOpenGL::hasOpenGLES()) { vertSource.append("#version 300 es\n"); } else { vertSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"); } #endif vertSource.append(vertHeader); QFile vertexShaderFile(":/" + vertPath); vertexShaderFile.open(QIODevice::ReadOnly); vertSource.append(vertexShaderFile.readAll()); result = shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource); if (!result) throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add vertex shader source from file", vertPath, shader->log())); // Load fragment shader QByteArray fragSource; // XXX Check can be removed and set to the MAC version after we move to Qt5.7 #ifdef Q_OS_MACOS fragSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n"); // OpenColorIO doesn't support the new GLSL version yet. fragSource.append("#define texture2D texture\n"); fragSource.append("#define texture3D texture\n"); #else if (KisOpenGL::hasOpenGLES()) { fragSource.append( "#version 300 es\n" "precision mediump float;\n" "precision mediump sampler3D;\n"); // OpenColorIO doesn't support the new GLSL version yet. fragSource.append("#define texture2D texture\n"); fragSource.append("#define texture3D texture\n"); } else { fragSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"); } #endif fragSource.append(fragHeader); QFile fragmentShaderFile(":/" + fragPath); fragmentShaderFile.open(QIODevice::ReadOnly); fragSource.append(fragmentShaderFile.readAll()); result = shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource); if (!result) throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add fragment shader source from file", fragPath, shader->log())); // Bind attributes shader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE); shader->bindAttributeLocation("a_textureCoordinate", PROGRAM_TEXCOORD_ATTRIBUTE); // Link result = shader->link(); if (!result) throw ShaderLoaderException(QString("Failed to link shader: ").append(vertPath)); Q_ASSERT(shader->isLinked()); return shader; } /** * Specific display shader loading function. It adds the appropriate extra code * to the fragment shader depending on what is available on the target machine. * Additionally, it picks the appropriate shader files depending on the availability * of OpenGL3. */ KisShaderProgram *KisOpenGLShaderLoader::loadDisplayShader(QSharedPointer displayFilter, bool useHiQualityFiltering) { QByteArray fragHeader; if (KisOpenGL::supportsLoD()) { fragHeader.append("#define DIRECT_LOD_FETCH\n"); if (useHiQualityFiltering) { fragHeader.append("#define HIGHQ_SCALING\n"); } } // If we have an OCIO display filter and it contains a function we add // it to our shader header which will sit on top of the fragment code. bool haveDisplayFilter = displayFilter && !displayFilter->program().isEmpty(); if (haveDisplayFilter) { fragHeader.append("#define USE_OCIO\n"); fragHeader.append(displayFilter->program().toLatin1()); } QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "highq_downscale.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "simple_texture_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), fragHeader); return shader; } /** * Specific checker shader loading function. It picks the appropriate shader * files depending on the availability of OpenGL3 on the target machine. */ KisShaderProgram *KisOpenGLShaderLoader::loadCheckerShader() { QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "simple_texture.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "simple_texture_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); return shader; } /** * Specific uniform shader loading function. It picks the appropriate shader * files depending on the availability of OpenGL3 on the target machine. */ KisShaderProgram *KisOpenGLShaderLoader::loadSolidColorShader() { QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "solid_color.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "solid_color_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); return shader; } + +KisShaderProgram *KisOpenGLShaderLoader::loadOverlayInvertedShader() +{ + QString vertPath, fragPath; + + // Select appropriate shader files + if ((KisOpenGL::hasOpenGL3() || KisOpenGL::hasOpenGLES()) && KisOpenGL::supportsRenderToFBO()) { + vertPath = "matrix_transform.vert"; + fragPath = "overlay_inverted.frag"; + } else { + vertPath = "matrix_transform_legacy.vert"; + fragPath = "solid_color_legacy.frag"; + } + + KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); + + return shader; +} diff --git a/libs/ui/opengl/kis_opengl_shader_loader.h b/libs/ui/opengl/kis_opengl_shader_loader.h index 0ab064032d..801bc05dca 100644 --- a/libs/ui/opengl/kis_opengl_shader_loader.h +++ b/libs/ui/opengl/kis_opengl_shader_loader.h @@ -1,91 +1,92 @@ /* * Copyright (C) Julian Thijssen , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "canvas/kis_display_filter.h" #include #include #include #include #include #include #include /** * An enum for storing all uniform names used in shaders */ enum Uniform { ModelViewProjection, TextureMatrix, ViewportScale, TexelSize, Texture0, Texture1, FixedLodLevel, FragmentColor }; /** * A wrapper class over Qt's QOpenGLShaderProgram to * provide access to uniform locations without * having to store them as constants next to the shader. */ class KisShaderProgram : public QOpenGLShaderProgram { public: /** * Stores the mapping of uniform enums to actual shader uniform names. * The actual shader names are necessary for calls to uniformLocation. */ static std::map names; /** * Stores uniform location in cache if it is called for the first time * and retrieves the location from the map on subsequent calls. */ int location(Uniform uniform) { std::map::const_iterator it = locationMap.find(uniform); if (it != locationMap.end()) { return it->second; } else { int location = uniformLocation(names[uniform]); locationMap[uniform] = location; return location; } } private: std::map locationMap; }; /** * A wrapper class over C++ Runtime Error, specifically to record * failures in shader compilation. Only thrown in KisOpenGLShaderLoader. */ class ShaderLoaderException : public std::runtime_error { public: ShaderLoaderException(QString error) : std::runtime_error(error.toStdString()) { } }; /** * A utility class for loading various shaders we use in Krita. It provides * specific methods for shaders that pick the correct vertex and fragment files * depending on the availability of OpenGL3. Additionally, it provides a generic * shader loading method to prevent duplication. */ class KisOpenGLShaderLoader { public: KisShaderProgram *loadDisplayShader(QSharedPointer displayFilter, bool useHiQualityFiltering); KisShaderProgram *loadCheckerShader(); KisShaderProgram *loadSolidColorShader(); + KisShaderProgram *loadOverlayInvertedShader(); private: KisShaderProgram *loadShader(QString vertPath, QString fragPath, QByteArray vertHeader, QByteArray fragHeader); };