[Libreoffice-commits] core.git: vcl/inc vcl/README.vars vcl/skia

Luboš Luňák (via logerrit) logerrit at kemper.freedesktop.org
Thu May 14 09:58:05 UTC 2020


 vcl/README.vars         |    1 
 vcl/inc/skia/utils.hxx  |   21 ++++++++
 vcl/skia/SkiaHelper.cxx |   79 +++++++++++++++++++++++++++++++
 vcl/skia/gdiimpl.cxx    |  121 +++++++++++++++++++++++++++++++++++++++---------
 4 files changed, 201 insertions(+), 21 deletions(-)

New commits:
commit fc0bff85f3338cb4fe8f4d42421cb69801cb3abb
Author:     Luboš Luňák <l.lunak at collabora.com>
AuthorDate: Wed May 13 13:08:00 2020 +0200
Commit:     Luboš Luňák <l.lunak at collabora.com>
CommitDate: Thu May 14 11:57:30 2020 +0200

    cache results of Skia's drawTransformedBitmap() (tdf#132438)
    
    E.g. scrolling in Writer with a huge image inserted requires scaling
    down on every paint, so cache the result if it's expensive.
    
    Change-Id: I9db040eab47e0e9d7fd416ad064caf0301d346fb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94118
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <l.lunak at collabora.com>

diff --git a/vcl/README.vars b/vcl/README.vars
index 495c9679e401..9f5a10a26a96 100644
--- a/vcl/README.vars
+++ b/vcl/README.vars
@@ -44,6 +44,7 @@ SAL_DISABLESKIA=1 - force disabled Skia
 SAL_ENABLESKIA=1 - enable Skia, unless blacklisted (and if the VCL backend supports Skia)
 SAL_FORCESKIA=1 - force using Skia, even if blacklisted
 SAL_SKIA=raster|vulkan - select Skia's drawing method, by default Vulkan is used
+SAL_DISABLE_SKIA_CACHE=1 - disable caching of complex images
 
 OpenGL,Skia
 -----------
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
index 942b5c3b88ef..e0fcf70c30e7 100644
--- a/vcl/inc/skia/utils.hxx
+++ b/vcl/inc/skia/utils.hxx
@@ -52,6 +52,11 @@ VCL_DLLPUBLIC sk_sp<SkImage> createSkImage(const SkBitmap& bitmap);
 VCL_DLLPUBLIC void
     prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContext)(bool));
 
+// Shared cache of images.
+void addCachedImage(const OString& key, sk_sp<SkImage> image);
+sk_sp<SkImage> findCachedImage(const OString& key);
+void removeCachedImage(sk_sp<SkImage> image);
+
 #ifdef DBG_UTIL
 void prefillSurface(sk_sp<SkSurface>& surface);
 VCL_DLLPUBLIC void dump(const SkBitmap& bitmap, const char* file);
@@ -104,6 +109,22 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
     return stream;
 }
 
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+                                                     const SkImage& image)
+{
+    // G - on GPU
+    return stream << static_cast<const void*>(&image) << " " << Size(image.width(), image.height())
+                  << "/" << (SkColorTypeBytesPerPixel(image.imageInfo().colorType()) * 8)
+                  << (image.isTextureBacked() ? "G" : "");
+}
+template <typename charT, typename traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& stream,
+                                                     const sk_sp<SkImage>& image)
+{
+    return stream << *image;
+}
+
 #endif // INCLUDED_VCL_INC_SKIA_UTILS_H
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/skia/SkiaHelper.cxx b/vcl/skia/SkiaHelper.cxx
index f0d2559d376f..d7004a7afcb7 100644
--- a/vcl/skia/SkiaHelper.cxx
+++ b/vcl/skia/SkiaHelper.cxx
@@ -31,6 +31,7 @@ bool isVCLSkiaEnabled() { return false; }
 #include <config_folders.h>
 #include <osl/file.hxx>
 #include <tools/stream.hxx>
+#include <list>
 
 #include <SkCanvas.h>
 #include <SkPaint.h>
@@ -439,10 +440,88 @@ sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
     return image;
 }
 
+namespace
+{
+// Image cache, for saving results of complex operations such as drawTransformedBitmap().
+struct ImageCacheItem
+{
+    OString key;
+    sk_sp<SkImage> image;
+    int size; // cost of the item
+};
+} //namespace
+
+// LRU cache, last item is the least recently used. Hopefully there won't be that many items
+// to require a hash/map. Using o3tl::lru_cache would be simpler, but it doesn't support
+// calculating cost of each item.
+static std::list<ImageCacheItem>* imageCache = nullptr;
+static int imageCacheSize = 0; // sum of all ImageCacheItem.size
+
+void addCachedImage(const OString& key, sk_sp<SkImage> image)
+{
+    static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
+    if (disabled)
+        return;
+    if (imageCache == nullptr)
+        imageCache = new std::list<ImageCacheItem>;
+    int size = image->width() * image->height()
+               * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
+    imageCache->push_front({ key, image, size });
+    imageCacheSize += size;
+    SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
+    const int MAX_CACHE_SIZE = 4 * 1000 * 1000 * 4; // 4x 1000px 32bpp images, 16MiB
+    while (imageCacheSize > MAX_CACHE_SIZE)
+    {
+        assert(!imageCache->empty());
+        imageCacheSize -= imageCache->back().size;
+        SAL_INFO("vcl.skia.trace",
+                 "least used removal " << image << ":" << imageCache->back().size);
+        imageCache->pop_back();
+    }
+}
+
+sk_sp<SkImage> findCachedImage(const OString& key)
+{
+    if (imageCache != nullptr)
+    {
+        for (auto it = imageCache->begin(); it != imageCache->end(); ++it)
+        {
+            if (it->key == key)
+            {
+                sk_sp<SkImage> ret = it->image;
+                SAL_INFO("vcl.skia.trace", "findcachedimage " << it->image);
+                imageCache->splice(imageCache->begin(), *imageCache, it);
+                return ret;
+            }
+        }
+    }
+    return nullptr;
+}
+
+void removeCachedImage(sk_sp<SkImage> image)
+{
+    if (imageCache == nullptr)
+        return;
+    for (auto it = imageCache->begin(); it != imageCache->end();)
+    {
+        if (it->image == image)
+        {
+            imageCacheSize -= it->size;
+            assert(imageCacheSize >= 0);
+            it = imageCache->erase(it);
+        }
+        else
+            ++it;
+    }
+}
+
 void cleanup()
 {
     delete sharedGrContext;
     sharedGrContext = nullptr;
+    delete imageCache;
+    imageCache = nullptr;
+    imageCacheSize = 0;
 }
 
 // Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 9fc3b5005980..436a9d35d43d 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -1293,6 +1293,90 @@ void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkIma
     postDraw();
 }
 
+// Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
+// with the given target size. Result will be possibly cached, unless disabled.
+static sk_sp<SkImage> mergeBitmaps(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
+                                   const Size targetSize, bool blockCaching)
+{
+    sk_sp<SkImage> image;
+    OString key;
+    if (targetSize == bitmap.GetSize())
+        blockCaching = true; // probably not much point in caching if no scaling is involved
+    if (targetSize.Width() > bitmap.GetSize().Width()
+        || targetSize.Height() > bitmap.GetSize().Height())
+        blockCaching = true; // caching enlarging is probably wasteful and not worth it
+    if (bitmap.GetSize().Width() < 100 && bitmap.GetSize().Height() < 100)
+        blockCaching = true; // image too small to be worth caching
+    if (!blockCaching)
+    {
+        OStringBuffer keyBuf;
+        keyBuf.append("0x")
+            .append(reinterpret_cast<sal_IntPtr>(&bitmap), 16)
+            .append("_0x")
+            .append(reinterpret_cast<sal_IntPtr>(alphaBitmap), 16)
+            .append("_")
+            .append(static_cast<sal_Int64>(bitmap.GetSkImage()->uniqueID()));
+        if (alphaBitmap)
+            keyBuf.append("_").append(
+                static_cast<sal_Int64>(alphaBitmap->GetAlphaSkImage()->uniqueID()));
+        key = keyBuf.makeStringAndClear();
+        image = SkiaHelper::findCachedImage(key);
+        if (image)
+            return image;
+    }
+    // Combine bitmap + alpha bitmap into one temporary bitmap with alpha.
+    // If scaling is needed, first apply the alpha, then scale, otherwise the scaling might affect the alpha values.
+    if (alphaBitmap && targetSize != bitmap.GetSize())
+    {
+        sk_sp<SkSurface> mergedSurface = SkiaHelper::createSkSurface(bitmap.GetSize());
+        if (!mergedSurface)
+            return nullptr;
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+        mergedSurface->getCanvas()->drawImage(bitmap.GetSkImage(), 0, 0, &paint);
+        paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha
+        mergedSurface->getCanvas()->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint);
+        sk_sp<SkSurface> scaledSurface = SkiaHelper::createSkSurface(targetSize);
+        if (!scaledSurface)
+            return nullptr;
+        paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+        paint.setFilterQuality(kHigh_SkFilterQuality);
+        scaledSurface->getCanvas()->drawImageRect(
+            mergedSurface->makeImageSnapshot(),
+            SkRect::MakeXYWH(0, 0, bitmap.GetSize().Width(), bitmap.GetSize().Height()),
+            SkRect::MakeXYWH(0, 0, targetSize.Width(), targetSize.Height()), &paint);
+        image = scaledSurface->makeImageSnapshot();
+    }
+    else // No alpha or no scaling, scale directly.
+    {
+        sk_sp<SkSurface> tmpSurface = SkiaHelper::createSkSurface(targetSize);
+        if (!tmpSurface)
+            return nullptr;
+        SkCanvas* canvas = tmpSurface->getCanvas();
+        SkAutoCanvasRestore autoRestore(canvas, true);
+        SkPaint paint;
+        if (targetSize != bitmap.GetSize())
+        {
+            SkMatrix matrix;
+            matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
+            matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
+            canvas->concat(matrix);
+            paint.setFilterQuality(kHigh_SkFilterQuality);
+        }
+        paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+        canvas->drawImage(bitmap.GetSkImage(), 0, 0, &paint);
+        if (alphaBitmap != nullptr)
+        {
+            paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha
+            canvas->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint);
+        }
+        image = tmpSurface->makeImageSnapshot();
+    }
+    if (!blockCaching)
+        SkiaHelper::addCachedImage(key, image);
+    return image;
+}
+
 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
                                                 const basegfx::B2DPoint& rX,
                                                 const basegfx::B2DPoint& rY,
@@ -1305,44 +1389,39 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
     const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
     const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
 
-    sk_sp<SkSurface> tmpSurface = SkiaHelper::createSkSurface(rSourceBitmap.GetSize());
-    if (!tmpSurface)
-        return false;
-
-    // Combine bitmap + alpha bitmap into one temporary bitmap with alpha
-    SkCanvas* aCanvas = tmpSurface->getCanvas();
-    SkPaint aPaint;
-    aPaint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
-    aCanvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, &aPaint);
-    if (pSkiaAlphaBitmap != nullptr)
-    {
-        aPaint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha
-        aCanvas->drawImage(pSkiaAlphaBitmap->GetAlphaSkImage(), 0, 0, &aPaint);
-    }
     // setup the image transformation
-    // using the rNull, rX, rY points as destinations for the (0,0), (0,Width), (Height,0) source points
+    // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points
     const basegfx::B2DVector aXRel = rX - rNull;
     const basegfx::B2DVector aYRel = rY - rNull;
 
     const Size aSize = rSourceBitmap.GetSize();
 
+    preDraw();
+    SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << aSize << " " << rNull
+                                                        << ":" << rX << ":" << rY);
+
+    // TODO: How to cache properly skewed images?
+    bool blockCaching = (aXRel.getY() != 0 || aYRel.getX() != 0);
+    const Size imageSize(aXRel.getX(), aYRel.getY());
+    sk_sp<SkImage> imageToDraw
+        = mergeBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize, blockCaching);
+    if (!imageToDraw)
+        return false;
+
     SkMatrix aMatrix;
-    aMatrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
+    aMatrix.set(SkMatrix::kMScaleX, aXRel.getX() / imageToDraw->width());
     aMatrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
     aMatrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
-    aMatrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
+    aMatrix.set(SkMatrix::kMScaleY, aYRel.getY() / imageToDraw->height());
     aMatrix.set(SkMatrix::kMTransX, rNull.getX());
     aMatrix.set(SkMatrix::kMTransY, rNull.getY());
 
-    preDraw();
-    SAL_INFO("vcl.skia.trace",
-             "drawtransformedbitmap(" << this << "): " << rNull << ":" << rX << ":" << rY);
     {
         SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
         getDrawCanvas()->concat(aMatrix);
         SkPaint paint;
         paint.setFilterQuality(kHigh_SkFilterQuality);
-        getDrawCanvas()->drawImage(tmpSurface->makeImageSnapshot(), 0, 0, &paint);
+        getDrawCanvas()->drawImage(imageToDraw, 0, 0, &paint);
     }
     assert(!mXorMode);
     postDraw();


More information about the Libreoffice-commits mailing list