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