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>
This commit is contained in:
@@ -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
|
||||
-----------
|
||||
|
||||
@@ -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: */
|
||||
|
||||
@@ -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.
|
||||
|
||||
+100
-21
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user