Commit 1fdda43f authored by Alvin Wong's avatar Alvin Wong

Fix OpenGL canvas under fractional DPI scaling

The canvas wasn't pixel-perfect even at 100% zoom when the
devicePixelRatio is non-integral. This change fixes the issue by
teaching Krita to perceive the widget/viewport size in the same manner
as Qt sets up the viewport, and to align the documentOffset to device
pixels.

Maniphest Tasks: T2299, T10765
Differential Revision: https://phabricator.kde.org/D20097
parent 948a30ae
......@@ -1052,7 +1052,23 @@ KisImageWSP KisCanvas2::currentImage() const
void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
{
QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
m_d->coordinatesConverter->setDocumentOffset(documentOffset);
qreal devicePixelRatio = m_d->coordinatesConverter->devicePixelRatio();
// The given offset is in widget logical pixels. In order to prevent fuzzy
// canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio
// is not integral, we adjusts the offset to map to whole device pixels.
// We use qFloor here since the offset can be negative.
int deviceOffsetX = qFloor(documentOffset.x() * devicePixelRatio);
int deviceOffsetY = qFloor(documentOffset.y() * devicePixelRatio);
// These adjusted offsets will be in logical pixel but is aligned in device
// pixel space for pixel-perfect rendering.
qreal pixelPerfectOffsetX = deviceOffsetX / devicePixelRatio;
qreal pixelPerfectOffsetY = deviceOffsetY / devicePixelRatio;
// FIXME: This is a temporary hack for fixing the canvas under fractional
// DPI scaling before a new coordinate system is introduced.
QPointF offsetAdjusted(pixelPerfectOffsetX, pixelPerfectOffsetY);
m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted);
QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
QPointF moveOffset = offsetAfter - offsetBefore;
......
......@@ -150,7 +150,7 @@ KisCoordinatesConverter::~KisCoordinatesConverter()
delete m_d;
}
void KisCoordinatesConverter::setCanvasWidgetSize(QSize size)
void KisCoordinatesConverter::setCanvasWidgetSize(QSizeF size)
{
m_d->canvasWidgetSize = size;
recalculateTransformations();
......@@ -167,7 +167,7 @@ void KisCoordinatesConverter::setImage(KisImageWSP image)
recalculateTransformations();
}
void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset)
void KisCoordinatesConverter::setDocumentOffset(const QPointF& offset)
{
QPointF diff = m_d->documentOffset - offset;
......@@ -176,6 +176,11 @@ void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset)
recalculateTransformations();
}
qreal KisCoordinatesConverter::devicePixelRatio() const
{
return m_d->devicePixelRatio;
}
QPoint KisCoordinatesConverter::documentOffset() const
{
return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y()));
......
......@@ -59,11 +59,12 @@ public:
KisCoordinatesConverter();
~KisCoordinatesConverter() override;
void setCanvasWidgetSize(QSize size);
void setCanvasWidgetSize(QSizeF size);
void setDevicePixelRatio(qreal value);
void setImage(KisImageWSP image);
void setDocumentOffset(const QPoint &offset);
void setDocumentOffset(const QPointF &offset);
qreal devicePixelRatio() const;
QPoint documentOffset() const;
qreal rotationAngle() const;
......
......@@ -382,9 +382,11 @@ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context)
cfg.setCanvasState("OPENGL_FAILED");
}
void KisOpenGLCanvas2::resizeGL(int width, int height)
void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/)
{
coordinatesConverter()->setCanvasWidgetSize(QSize(width, height));
// The given size is the widget size but here we actually want to give
// KisCoordinatesConverter the viewport size aligned to device pixels.
coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel());
paintGL();
}
......@@ -421,10 +423,14 @@ void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path)
return;
}
QSizeF widgetSize = widgetSizeAlignedToDevicePixel();
// setup the mvp transformation
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// 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()->flakeToWidgetTransform());
......@@ -521,9 +527,10 @@ void KisOpenGLCanvas2::drawCheckers()
QRectF textureRect;
QRectF modelRect;
QSizeF widgetSize = widgetSizeAlignedToDevicePixel();
QRectF viewportRect = !d->wrapAroundMode ?
converter->imageRectInViewportPixels() :
converter->widgetToViewport(this->rect());
converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height()));
if (!canvas()->renderingLimit().isEmpty()) {
const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect();
......@@ -543,7 +550,9 @@ void KisOpenGLCanvas2::drawCheckers()
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// 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);
......@@ -591,9 +600,13 @@ void KisOpenGLCanvas2::drawGrid()
return;
}
QSizeF widgetSize = widgetSizeAlignedToDevicePixel();
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// 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());
......@@ -613,7 +626,7 @@ void KisOpenGLCanvas2::drawGrid()
d->lineBuffer.bind();
}
QRectF widgetRect(0,0, width(), height());
QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect));
QRect wr = widgetRectInImagePixels.toAlignedRect();
......@@ -666,9 +679,13 @@ void KisOpenGLCanvas2::drawImage()
d->displayShader->bind();
QSizeF widgetSize = widgetSizeAlignedToDevicePixel();
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// 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());
......@@ -680,7 +697,7 @@ void KisOpenGLCanvas2::drawImage()
textureMatrix.setToIdentity();
d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix);
QRectF widgetRect(0,0, width(), height());
QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height());
QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect));
const QRect renderingLimit = canvas()->renderingLimit();
......@@ -838,6 +855,23 @@ void KisOpenGLCanvas2::drawImage()
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<int>(width() * devicePixelRatioF());
int viewportHeight = static_cast<int>(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);
......
......@@ -119,6 +119,8 @@ private:
void drawImage();
void drawCheckers();
void drawGrid();
QSize viewportDevicePixelSize() const;
QSizeF widgetSizeAlignedToDevicePixel() const;
private:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment