summaryrefslogtreecommitdiff
path: root/vcl/skia
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 /vcl/skia
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>
Diffstat (limited to 'vcl/skia')
-rw-r--r--vcl/skia/SkiaHelper.cxx79
-rw-r--r--vcl/skia/gdiimpl.cxx121
2 files changed, 179 insertions, 21 deletions
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();