RepositoryExternal.mk   |    1 +
 vcl/inc/skia/utils.hxx  |    4 ++++
 vcl/skia/SkiaHelper.cxx |   35 ++++++++++++++++++++++++++++++++++-
 vcl/skia/salbmp.cxx     |   14 ++++++++++++--
 4 files changed, 51 insertions(+), 3 deletions(-)

New commits:
commit 2c86b79e87bc8579f5213708954d5c85fe231407
Author:     Luboš Luňák <l.lu...@collabora.com>
AuthorDate: Thu Dec 9 14:30:15 2021 +0100
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Fri Dec 10 11:59:39 2021 +0100

    cache Skia drawing based on checksum of bitmap content (tdf#146095)
    
    Previously the caching was based on SkImage's uniqueID(), which
    detects whether it's the same image. For caching to work based on
    this it is required that the underlying image does not change,
    which generally means using the same Bitmap(Ex) for repeated drawing.
    But e.g. in tdf#146096 canvas (AFAICT) tries to cache bitmaps
    by copying them around, which generates so many bitmaps that they all
    do not fit in the cache (helped by the fact that the edit window
    still animates them too, and bitmap caching in canvas being broken).
    
    It feels kinda lame and unnecessary to checksum pixels of many bitmaps
    to be drawn just to find out whether their drawing can be sped up,
    and it really should be fixed higher up wherever it's broken, but
    I've already failed several times trying to fix this in canvas,
    so let's just roll with this.
    
    This is done only for raster-based images, because GPU-backed drawing
    is fast enough to deal even with expensive drawing (and fetching
    GPU-backend pixels would be expensive).
    
    On my machine this changes showing the slide from not being able
    to quite keep up to about 20% CPU usage.
    
    Change-Id: I25a362a02dc61e99b391cb305e2fdcd2feb67879
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126613
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <l.lu...@collabora.com>

diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk
index e63ab24dba27..fbb02d802142 100644
--- a/RepositoryExternal.mk
+++ b/RepositoryExternal.mk
@@ -128,6 +128,7 @@ $(call gb_LinkTarget_set_include,$(1),\
        -I$(call gb_UnpackedTarball_get_dir,skia)/include/gpu \
        -I$(call gb_UnpackedTarball_get_dir,skia)/include/config \
        -I$(call gb_UnpackedTarball_get_dir,skia)/include/ports \
+       -I$(call gb_UnpackedTarball_get_dir,skia)/include/private \
        -I$(call gb_UnpackedTarball_get_dir,skia)/include/third_party/vulkan \
        -I$(call gb_UnpackedTarball_get_dir,skia)/tools/gpu \
        -I$(call gb_UnpackedTarball_get_dir,skia) \
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
index 35180c016e24..d2d4c81c3f94 100644
--- a/vcl/inc/skia/utils.hxx
+++ b/vcl/inc/skia/utils.hxx
@@ -119,6 +119,10 @@ sk_sp<SkImage> findCachedImage(const OString& key);
 void removeCachedImage(sk_sp<SkImage> image);
 tools::Long maxImageCacheSize();
 
+// Get checksum of the image content, only for raster images. Is cached,
+// but may still be somewhat expensive.
+uint32_t getSkImageChecksum(sk_sp<SkImage> image);
+
 // SkSurfaceProps to be used by all Skia surfaces.
 VCL_DLLPUBLIC const SkSurfaceProps* surfaceProps();
 // Set pixel geometry to be used by SkSurfaceProps.
diff --git a/vcl/skia/SkiaHelper.cxx b/vcl/skia/SkiaHelper.cxx
index 5b022d51b50d..2a4a7f1a6d7f 100644
--- a/vcl/skia/SkiaHelper.cxx
+++ b/vcl/skia/SkiaHelper.cxx
@@ -36,6 +36,7 @@ bool isVCLSkiaEnabled() { return false; }
 #include <osl/file.hxx>
 #include <tools/stream.hxx>
 #include <list>
+#include <o3tl/lru_map.hxx>
 
 #include <SkCanvas.h>
 #include <SkPaint.h>
@@ -43,6 +44,7 @@ bool isVCLSkiaEnabled() { return false; }
 #include <SkGraphics.h>
 #include <GrDirectContext.h>
 #include <SkRuntimeEffect.h>
+#include <SkOpts_spi.h>
 #include <skia_compiler.hxx>
 #include <skia_opts.hxx>
 #include <tools/sk_app/VulkanWindowContext.h>
@@ -581,7 +583,7 @@ struct ImageCacheItem
 } //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
+// to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't 
support
 // calculating cost of each item.
 static std::list<ImageCacheItem> imageCache;
 static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
@@ -644,6 +646,37 @@ tools::Long maxImageCacheSize()
     return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
 }
 
+static o3tl::lru_map<uint32_t, uint32_t> checksumCache(256);
+
+uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
+{
+    // Use uint32_t because that's what SkOpts::hash_fn() returns.
+    static_assert(std::is_same_v<uint32_t, decltype(SkOpts::hash_fn(nullptr, 
0, 0))>);
+    const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel();
+    if (dataRowBytes == pixmap.rowBytes())
+        return SkOpts::hash_fn(pixmap.addr(), pixmap.height() * dataRowBytes, 
0);
+    uint32_t sum = 0;
+    for (int row = 0; row < pixmap.height(); ++row)
+        sum = SkOpts::hash_fn(pixmap.addr(0, row), dataRowBytes, sum);
+    return sum;
+}
+
+uint32_t getSkImageChecksum(sk_sp<SkImage> image)
+{
+    // Cache the checksums based on the uniqueID() (which should stay the same
+    // for the same image), because it may be still somewhat expensive.
+    uint32_t id = image->uniqueID();
+    auto it = checksumCache.find(id);
+    if (it != checksumCache.end())
+        return it->second;
+    SkPixmap pixmap;
+    if (!image->peekPixels(&pixmap))
+        abort(); // Fetching of GPU-based pixels is expensive, and 
shouldn't(?) be needed anyway.
+    uint32_t checksum = computeSkPixmapChecksum(pixmap);
+    checksumCache.insert({ id, checksum });
+    return checksum;
+}
+
 static sk_sp<SkBlender> invertBlender;
 static sk_sp<SkBlender> xorBlender;
 
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
index c18ad18f8428..b81e55221f34 100644
--- a/vcl/skia/salbmp.cxx
+++ b/vcl/skia/salbmp.cxx
@@ -1357,7 +1357,14 @@ OString SkiaSalBitmap::GetImageKey(DirectImage direct) 
const
         return OString::Concat("E") + ss.str().c_str();
     }
     assert(direct == DirectImage::No || mImage);
-    return OString::Concat("I") + 
OString::number(GetSkImage(direct)->uniqueID());
+    sk_sp<SkImage> image = GetSkImage(direct);
+    // In some cases drawing code may try to draw the same content but using
+    // different bitmaps (even underlying bitmaps), for example canvas 
apparently
+    // copies the same things around in tdf#146095. For pixel-based images
+    // it should be still cheaper to compute a checksum and avoid re-caching.
+    if (!image->isTextureBacked())
+        return OString::Concat("C") + 
OString::number(getSkImageChecksum(image));
+    return OString::Concat("I") + OString::number(image->uniqueID());
 }
 
 OString SkiaSalBitmap::GetAlphaImageKey(DirectImage direct) const
@@ -1370,7 +1377,10 @@ OString SkiaSalBitmap::GetAlphaImageKey(DirectImage 
direct) const
         return OString::Concat("E") + ss.str().c_str();
     }
     assert(direct == DirectImage::No || mAlphaImage);
-    return OString::Concat("I") + 
OString::number(GetAlphaSkImage(direct)->uniqueID());
+    sk_sp<SkImage> image = GetAlphaSkImage(direct);
+    if (!image->isTextureBacked())
+        return OString::Concat("C") + 
OString::number(getSkImageChecksum(image));
+    return OString::Concat("I") + OString::number(image->uniqueID());
 }
 
 void SkiaSalBitmap::dump(const char* file) const

Reply via email to