diff options
author | Luboš Luňák <l.lunak@collabora.com> | 2020-05-13 13:08:00 +0200 |
---|---|---|
committer | Luboš Luňák <l.lunak@collabora.com> | 2020-05-14 11:57:30 +0200 |
commit | fc0bff85f3338cb4fe8f4d42421cb69801cb3abb (patch) | |
tree | 331faaa9f3c83bab09c3a353900a0ad21b27c725 | |
parent | 13a834c26d28f3cc08f106bf5ec1b71d4bc1f418 (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.vars | 1 | ||||
-rw-r--r-- | vcl/inc/skia/utils.hxx | 21 | ||||
-rw-r--r-- | vcl/skia/SkiaHelper.cxx | 79 | ||||
-rw-r--r-- | vcl/skia/gdiimpl.cxx | 121 |
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(); |