summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuboš Luňák <l.lunak@collabora.com>2020-05-13 13:08:00 +0200
committerLuboš Luňák <l.lunak@collabora.com>2020-05-14 11:57:30 +0200
commitfc0bff85f3338cb4fe8f4d42421cb69801cb3abb (patch)
tree331faaa9f3c83bab09c3a353900a0ad21b27c725
parent13a834c26d28f3cc08f106bf5ec1b71d4bc1f418 (diff)
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@collabora.com>
-rw-r--r--vcl/README.vars1
-rw-r--r--vcl/inc/skia/utils.hxx21
-rw-r--r--vcl/skia/SkiaHelper.cxx79
-rw-r--r--vcl/skia/gdiimpl.cxx121
4 files changed, 201 insertions, 21 deletions
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();