summaryrefslogtreecommitdiff
path: root/vcl/inc/skia
diff options
context:
space:
mode:
authorLuboš Luňák <l.lunak@collabora.com>2021-11-11 14:01:55 +0100
committerLuboš Luňák <l.lunak@collabora.com>2021-11-16 10:38:54 +0100
commitb5983dbe2c41f38e653201574cf20cd4bd76e950 (patch)
treea8254c2dc2b9ef2c7d91aa9dbf2b6994b3295645 /vcl/inc/skia
parent67669707e0c6c8a390d352e7060ad5862d727433 (diff)
implement HiDPI support for Skia/Mac (tdf#144214)
The basic idea is the same as the 'aqua' backend, simply set up a scaling matrix for all drawing. That will take care of the basic drawing everything twice as large, which is twice the resolution. And then blit this data to the window, which expects data this way. Converting back from backing surface needs explicit coordinate conversions, and when converting to a bitmap the bitmap needs to be scaled down in order to appear normally sized. Fortunately I've already implemented delayed scaling, which means that if the bitmap is drawn later again without any modifications, no data would be lost (to be done in a follow-up commit). Unittests occassionally need special handling, as such scaling down to bitmap not being smoothed, because they expect exact color values. Change-Id: Ieadf2c3693f7c9676c31c7394d46299addf7880c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125060 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
Diffstat (limited to 'vcl/inc/skia')
-rw-r--r--vcl/inc/skia/gdiimpl.hxx20
-rw-r--r--vcl/inc/skia/osx/gdiimpl.hxx3
-rw-r--r--vcl/inc/skia/utils.hxx61
3 files changed, 70 insertions, 14 deletions
diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx
index 03a4d5cf0413..70bbcf5c4dcc 100644
--- a/vcl/inc/skia/gdiimpl.hxx
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -239,6 +239,7 @@ protected:
void privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
tools::Long nHeight, double nTransparency, bool blockAA = false);
+ void privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src);
void setProvider(SalGeometryProvider* provider) { mProvider = provider; }
@@ -256,6 +257,8 @@ protected:
int GetWidth() const { return mProvider ? mProvider->GetWidth() : 1; }
// get the height of the device
int GetHeight() const { return mProvider ? mProvider->GetHeight() : 1; }
+ // Get the global HiDPI scaling factor.
+ virtual int getWindowScaling() const;
SkCanvas* getXorCanvas();
void applyXor();
@@ -277,6 +280,8 @@ protected:
// and swapping to the screen is not _that_slow.
mDirtyRect.join(addedRect);
}
+ void setCanvasScalingAndClipping();
+ void resetCanvasScalingAndClipping();
static void setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region);
sk_sp<SkImage> mergeCacheBitmaps(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
const Size targetSize);
@@ -305,9 +310,12 @@ protected:
if (graphics == nullptr)
return stream << "(null)";
// O - offscreen, G - GPU-based, R - raster
- return stream << static_cast<const void*>(graphics) << " "
- << Size(graphics->GetWidth(), graphics->GetHeight())
- << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ stream << static_cast<const void*>(graphics) << " "
+ << Size(graphics->GetWidth(), graphics->GetHeight());
+ if (graphics->mScaling != 1)
+ stream << "*" << graphics->mScaling;
+ stream << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ return stream;
}
SalGraphics& mParent;
@@ -318,14 +326,15 @@ protected:
// Note that mSurface may be a proxy surface and not the one from the window context.
std::unique_ptr<sk_app::WindowContext> mWindowContext;
bool mIsGPU; // whether the surface is GPU-backed
- SkIRect mDirtyRect; // the area that has been changed since the last performFlush()
+ // Note that we generally use VCL coordinates, which is not mSurface coordinates if mScaling!=1.
+ SkIRect mDirtyRect; // The area that has been changed since the last performFlush().
vcl::Region mClipRegion;
+ SkRegion mXorRegion; // The area that needs updating for the xor operation.
Color mLineColor;
Color mFillColor;
bool mXorMode;
SkBitmap mXorBitmap;
std::unique_ptr<SkCanvas> mXorCanvas;
- SkRegion mXorRegion; // the area that needs updating for the xor operation
std::unique_ptr<SkiaFlushIdle> mFlush;
// Info about pending polygons to draw (we try to merge adjacent polygons into one).
struct LastPolyPolygonInfo
@@ -336,6 +345,7 @@ protected:
};
LastPolyPolygonInfo mLastPolyPolygonInfo;
int mPendingOperationsToFlush;
+ int mScaling; // The scale factor for HiDPI screens.
};
#endif
diff --git a/vcl/inc/skia/osx/gdiimpl.hxx b/vcl/inc/skia/osx/gdiimpl.hxx
index c4892ab45b43..42a8257f8b8f 100644
--- a/vcl/inc/skia/osx/gdiimpl.hxx
+++ b/vcl/inc/skia/osx/gdiimpl.hxx
@@ -43,6 +43,9 @@ public:
virtual void Flush() override;
virtual void Flush(const tools::Rectangle&) override;
+protected:
+ virtual int getWindowScaling() const override;
+
private:
virtual void createWindowSurfaceInternal(bool forceRaster = false) override;
virtual void flushSurfaceToWindowContext() override;
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
index ba479c58f234..ed404f7cc3eb 100644
--- a/vcl/inc/skia/utils.hxx
+++ b/vcl/inc/skia/utils.hxx
@@ -33,6 +33,8 @@
#include <tools/sk_app/WindowContext.h>
#include <postmac.h>
+#include <string_view>
+
namespace SkiaHelper
{
// Get the one shared GrDirectContext instance.
@@ -90,6 +92,17 @@ VCL_DLLPUBLIC const SkSurfaceProps* surfaceProps();
// Set pixel geometry to be used by SkSurfaceProps.
VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);
+inline bool isUnitTestRunning(const char* name = nullptr)
+{
+ if (name == nullptr)
+ {
+ static const char* const testname = getenv("LO_TESTNAME");
+ return testname != nullptr;
+ }
+ const char* const testname = getenv("LO_TESTNAME");
+ return testname != nullptr && std::string_view(name) == testname;
+}
+
// Normal scaling algorithms have a poor quality when downscaling a lot.
// https://bugs.chromium.org/p/skia/issues/detail?id=11810 suggests to use mipmaps
// in such a case, which is annoying to do explicitly instead of Skia deciding which
@@ -98,11 +111,14 @@ VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);
// Anything scaled down at least this ratio will use linear+mipmaps.
constexpr int downscaleRatioThreshold = 4;
-inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatrix& matrix)
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, SkMatrix matrix,
+ int scalingFactor)
{
- switch (scaling)
+ switch (scalingType)
{
case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ matrix.postScale(scalingFactor, scalingFactor);
if (matrix.getScaleX() <= 1.0 / downscaleRatioThreshold
|| matrix.getScaleY() <= 1.0 / downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
@@ -110,6 +126,7 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatri
case BmpScaleFlag::Default:
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
default:
assert(false);
@@ -117,12 +134,14 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatri
}
}
-inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& srcSize,
- const Size& destSize)
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, const Size& srcSize,
+ Size destSize, int scalingFactor)
{
- switch (scaling)
+ switch (scalingType)
{
case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
|| srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
@@ -130,6 +149,7 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& s
case BmpScaleFlag::Default:
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
default:
assert(false);
@@ -137,18 +157,41 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& s
}
}
-inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry)
+inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry, int scalingFactor,
+ int srcScalingFactor = 1)
{
- if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ // If there will be scaling, make it smooth, but not in unittests, as those often
+ // require exact color values and would be confused by this.
+ if (isUnitTestRunning())
+ return SkSamplingOptions(); // none
+ Size srcSize(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ Size destSize(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
+ if (srcScalingFactor != 1)
+ srcSize *= srcScalingFactor;
+ if (srcSize != destSize)
{
- if (rPosAry.mnSrcWidth / rPosAry.mnDestWidth >= downscaleRatioThreshold
- || rPosAry.mnSrcHeight / rPosAry.mnDestHeight >= downscaleRatioThreshold)
+ if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
+ || srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
return SkSamplingOptions(SkCubicResampler::Mitchell()); // best
}
return SkSamplingOptions(); // none
}
+inline SkRect scaleRect(const SkRect& rect, int scaling)
+{
+ return SkRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
+inline SkIRect scaleRect(const SkIRect& rect, int scaling)
+{
+ return SkIRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
#ifdef DBG_UTIL
void prefillSurface(const sk_sp<SkSurface>& surface);
VCL_DLLPUBLIC void dump(const SkBitmap& bitmap, const char* file);