external/skia/UnpackedTarball_skia.mk | 1 external/skia/extend-rgb-to-rgba.patch.0 | 23 +++ vcl/inc/skia/salbmp.hxx | 7 + vcl/skia/salbmp.cxx | 198 ++++++++++++++++++++++++------- 4 files changed, 187 insertions(+), 42 deletions(-)
New commits: commit c27d2e9145be8972e5d2174fb3f317dc08930074 Author: Luboš Luňák <l.lu...@collabora.com> AuthorDate: Thu Apr 2 12:33:57 2020 +0200 Commit: Luboš Luňák <l.lu...@collabora.com> CommitDate: Tue Apr 7 11:52:43 2020 +0200 optimize bit depth conversions to/from Skia where possible Skia has an optimized function for RGB->RGBA conversion that we are going to do often because 24bpp is the most common LO image format. The function is private, so patch that. Also optimize 32bpp->8bpp conversion with gray palette. Change-Id: I48b06f80d5ca9e1c8e3ee51da61e87541bd83d5a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/91768 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lu...@collabora.com> diff --git a/external/skia/UnpackedTarball_skia.mk b/external/skia/UnpackedTarball_skia.mk index 99344871cb9d..da0a2a7a0547 100644 --- a/external/skia/UnpackedTarball_skia.mk +++ b/external/skia/UnpackedTarball_skia.mk @@ -32,6 +32,7 @@ skia_patches := \ windows-force-unicode-api.patch.0 \ operator-eq-bool.patch.0 \ fix-without-gl.patch.0 \ + extend-rgb-to-rgba.patch.0 \ $(eval $(call gb_UnpackedTarball_set_patchlevel,skia,1)) diff --git a/external/skia/extend-rgb-to-rgba.patch.0 b/external/skia/extend-rgb-to-rgba.patch.0 new file mode 100644 index 000000000000..f68dbab96336 --- /dev/null +++ b/external/skia/extend-rgb-to-rgba.patch.0 @@ -0,0 +1,23 @@ +diff --git a/include/core/SkSwizzle.h b/include/core/SkSwizzle.h +index 61e93b2da7..c19063bb91 100644 +--- ./include/core/SkSwizzle.h ++++ ./include/core/SkSwizzle.h +@@ -16,4 +16,6 @@ + */ + SK_API void SkSwapRB(uint32_t* dest, const uint32_t* src, int count); + ++SK_API void SkExtendRGBToRGBA(uint32_t* dest, const uint8_t* src, int count); ++ + #endif +diff --git a/src/core/SkSwizzle.cpp b/src/core/SkSwizzle.cpp +index 301b0184f1..6e6dd27558 100644 +--- ./src/core/SkSwizzle.cpp ++++ ./src/core/SkSwizzle.cpp +@@ -12,3 +12,7 @@ + void SkSwapRB(uint32_t* dest, const uint32_t* src, int count) { + SkOpts::RGBA_to_BGRA(dest, src, count); + } ++ ++void SkExtendRGBToRGBA(uint32_t* dest, const uint8_t* src, int count) { ++ SkOpts::RGB_to_RGB1(dest, src, count); ++} diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx index f0bffb500194..c000b391eaa2 100644 --- a/vcl/skia/salbmp.cxx +++ b/vcl/skia/salbmp.cxx @@ -32,6 +32,7 @@ #include <SkImage.h> #include <SkPixelRef.h> #include <SkSurface.h> +#include <SkSwizzle.h> #include <skia/utils.hxx> #include <skia/zone.hxx> @@ -387,19 +388,16 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const else if (mBitCount == 24) { // Convert 24bpp RGB/BGR to 32bpp RGBA/BGRA. - std::unique_ptr<sal_uInt8[]> data( - new sal_uInt8[mPixelsSize.Height() * mPixelsSize.Width() * 4]); - sal_uInt8* dest = data.get(); + std::unique_ptr<uint32_t[]> data( + new uint32_t[mPixelsSize.Height() * mPixelsSize.Width()]); + uint32_t* dest = data.get(); for (long y = 0; y < mPixelsSize.Height(); ++y) { const sal_uInt8* src = mBuffer.get() + mScanlineSize * y; - for (long x = 0; x < mPixelsSize.Width(); ++x) - { - *dest++ = *src++; - *dest++ = *src++; - *dest++ = *src++; - *dest++ = 0xff; - } + // This also works as BGR to BGRA (the function extends 3 bytes to 4 + // by adding 0xFF alpha, so position of B and R doesn't matter). + SkExtendRGBToRGBA(dest, src, mPixelsSize.Width()); + dest += mPixelsSize.Width(); } if (!bitmap.installPixels( SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), @@ -647,6 +645,17 @@ void SkiaSalBitmap::EnsureBitmapData() } } } + else if (mBitCount == 8 && mPalette.IsGreyPalette()) + { + for (long y = 0; y < mSize.Height(); ++y) + { + const uint8_t* src = static_cast<uint8_t*>(mBitmap.getAddr(0, y)); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + // no actual data conversion, use one color channel as the gray value + for (long x = 0; x < mSize.Width(); ++x) + dest[x] = src[x * 4]; + } + } else { std::unique_ptr<vcl::ScanlineWriter> pWriter commit 912773d521fd6b8ff90d27f7bd3f5a46fd0df582 Author: Luboš Luňák <l.lu...@collabora.com> AuthorDate: Tue Mar 24 16:21:44 2020 +0100 Commit: Luboš Luňák <l.lu...@collabora.com> CommitDate: Tue Apr 7 11:52:32 2020 +0200 use delayed scaling in SalSkiaBitmap This allows doing the scaling at least in some cases on the GPU. Pending scaling is detected by mSize (logical size) != mPixelSize (actual size of pixel data). Change-Id: I8adef13750c195fdbe48c9167737a0c31cda66d5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/91767 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lu...@collabora.com> diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx index 762e45c420b8..a5f264ae2ba1 100644 --- a/vcl/inc/skia/salbmp.hxx +++ b/vcl/inc/skia/salbmp.hxx @@ -73,6 +73,8 @@ private: // Reset the cached images allocated in GetSkImage()/GetAlphaSkImage(), // and also the SkBitmap allocated in GetAsSkBitmap(). void ResetCachedData(); + // Sets the data only as SkImage (will be converted as needed). + void ResetToSkImage(sk_sp<SkImage> image); // Call to ensure mBuffer has data (will convert from mImage if necessary). void EnsureBitmapData(); void EnsureBitmapData() const { return const_cast<SkiaSalBitmap*>(this)->EnsureBitmapData(); } @@ -117,6 +119,11 @@ private: SkBitmap mBitmap; // cached mBuffer, if needed sk_sp<SkImage> mImage; // possibly GPU-backed sk_sp<SkImage> mAlphaImage; // cached contents as alpha image, possibly GPU-backed + // Actual scaling triggered by scale() is done on-demand. This is the size of the pixel + // data in mBuffer, if it differs from mSize, then there is a scaling operation pending. + Size mPixelsSize; + SkFilterQuality mScaleQuality = kHigh_SkFilterQuality; // quality for on-demand scaling + bool mDisableScale = false; // used to block our scale() #ifdef DBG_UTIL int mWriteAccessCount = 0; // number of write AcquireAccess() that have not been released #endif diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx index 27de5fc82508..f0bffb500194 100644 --- a/vcl/skia/salbmp.cxx +++ b/vcl/skia/salbmp.cxx @@ -58,7 +58,7 @@ SkiaSalBitmap::SkiaSalBitmap(const sk_sp<SkImage>& image) mImage = image; mPalette = BitmapPalette(); mBitCount = 32; - mSize = Size(image->width(), image->height()); + mSize = mPixelsSize = Size(image->width(), image->height()); #ifdef DBG_UTIL mWriteAccessCount = 0; #endif @@ -73,14 +73,14 @@ bool SkiaSalBitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const Bitmap return false; mPalette = rPal; mBitCount = nBitCount; - mSize = rSize; + mSize = mPixelsSize = rSize; #ifdef DBG_UTIL mWriteAccessCount = 0; #endif if (!CreateBitmapData()) { mBitCount = 0; - mSize = Size(); + mSize = mPixelsSize = Size(); mPalette = BitmapPalette(); return false; } @@ -146,6 +146,7 @@ bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount) mPalette = src.mPalette; mBitCount = src.mBitCount; mSize = src.mSize; + mPixelsSize = src.mPixelsSize; mScanlineSize = src.mScanlineSize; #ifdef DBG_UTIL mWriteAccessCount = 0; @@ -156,9 +157,7 @@ bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount) // about it and rely on EnsureBitmapData() doing the conversion from mImage // if needed, even if that may need unnecessary to- and from- SkImage // conversion. - GetSkImage(); // create mImage - mBitmap.reset(); - mBuffer.reset(); + ResetToSkImage(GetSkImage()); } SAL_INFO("vcl.skia.trace", "create(" << this << "): (" << &src << ")"); return true; @@ -282,7 +281,7 @@ bool SkiaSalBitmap::GetSystemData(BitmapSystemData&) return false; } -bool SkiaSalBitmap::ScalingSupported() const { return true; } +bool SkiaSalBitmap::ScalingSupported() const { return !mDisableScale; } bool SkiaSalBitmap::Scale(const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag) { @@ -297,32 +296,39 @@ bool SkiaSalBitmap::Scale(const double& rScaleX, const double& rScaleY, BmpScale SAL_INFO("vcl.skia.trace", "scale(" << this << "): " << mSize << "->" << newSize << ":" << static_cast<int>(nScaleFlag)); - SkPaint paint; + // The idea here is that the actual scaling will be delayed until the result + // is actually needed. Usually the scaled bitmap will be drawn somewhere, + // so delaying will mean the scaling can be done as a part of GetSkImage(). + // That means it can be GPU-accelerated, while done here directly it would need + // to be either done by CPU, or with the CPU->GPU->CPU roundtrip required + // by GPU-accelereated scaling. + // Pending scaling is detected by 'mSize != mPixelsSize'. + SkFilterQuality currentQuality; switch (nScaleFlag) { case BmpScaleFlag::Fast: - paint.setFilterQuality(kNone_SkFilterQuality); + currentQuality = kNone_SkFilterQuality; break; case BmpScaleFlag::Default: - paint.setFilterQuality(kMedium_SkFilterQuality); + currentQuality = kMedium_SkFilterQuality; break; case BmpScaleFlag::BestQuality: - paint.setFilterQuality(kHigh_SkFilterQuality); + currentQuality = kHigh_SkFilterQuality; break; default: return false; } - sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(newSize); - assert(surface); - paint.setBlendMode(SkBlendMode::kSrc); // draw as is, including alpha - surface->getCanvas()->drawImageRect( - GetSkImage(), SkRect::MakeXYWH(0, 0, mSize.Width(), mSize.Height()), - SkRect::MakeXYWH(0, 0, newSize.Width(), newSize.Height()), &paint); - // This will get generated from mImage if needed. - mBuffer.reset(); - ResetCachedData(); - mImage = surface->makeImageSnapshot(); + // if there is already one scale() pending, use the lowest quality of all requested + static_assert(kMedium_SkFilterQuality < kHigh_SkFilterQuality); + mScaleQuality = std::min(mScaleQuality, currentQuality); + // scaling will be actually done on-demand when needed, the need will be recognized + // by mSize != mPixelsSize mSize = newSize; + // Do not reset cached data if mImage is possibly the only data we have. + if (mBuffer) + ResetCachedData(); + // The rest will be handled when the scaled bitmap is actually needed, + // such as in EnsureBitmapData() or GetSkImage(). return true; } @@ -354,6 +360,7 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const if (!mBitmap.isNull()) return mBitmap; EnsureBitmapData(); + assert(mSize == mPixelsSize); // data has already been scaled if needed SkiaZone zone; SkBitmap bitmap; if (mBuffer) @@ -362,7 +369,7 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const { // Make a copy, the bitmap should be immutable (otherwise converting it // to SkImage will make a copy anyway). - const size_t bytes = mSize.Height() * mScanlineSize; + const size_t bytes = mPixelsSize.Height() * mScanlineSize; std::unique_ptr<sal_uInt8[]> data(new sal_uInt8[bytes]); memcpy(data.get(), mBuffer.get(), bytes); #if SKIA_USE_BITMAP32 @@ -371,8 +378,8 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const SkAlphaType alphaType = kUnpremul_SkAlphaType; #endif if (!bitmap.installPixels( - SkImageInfo::MakeS32(mSize.Width(), mSize.Height(), alphaType), data.release(), - mSize.Width() * 4, + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), alphaType), + data.release(), mPixelsSize.Width() * 4, [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) abort(); bitmap.setImmutable(); @@ -380,12 +387,13 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const else if (mBitCount == 24) { // Convert 24bpp RGB/BGR to 32bpp RGBA/BGRA. - std::unique_ptr<sal_uInt8[]> data(new sal_uInt8[mSize.Height() * mSize.Width() * 4]); + std::unique_ptr<sal_uInt8[]> data( + new sal_uInt8[mPixelsSize.Height() * mPixelsSize.Width() * 4]); sal_uInt8* dest = data.get(); - for (long y = 0; y < mSize.Height(); ++y) + for (long y = 0; y < mPixelsSize.Height(); ++y) { const sal_uInt8* src = mBuffer.get() + mScanlineSize * y; - for (long x = 0; x < mSize.Width(); ++x) + for (long x = 0; x < mPixelsSize.Width(); ++x) { *dest++ = *src++; *dest++ = *src++; @@ -394,8 +402,9 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const } } if (!bitmap.installPixels( - SkImageInfo::MakeS32(mSize.Width(), mSize.Height(), kOpaque_SkAlphaType), - data.release(), mSize.Width() * 4, + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), + kOpaque_SkAlphaType), + data.release(), mPixelsSize.Width() * 4, [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) abort(); bitmap.setImmutable(); @@ -408,12 +417,13 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const #define GET_FORMAT \ (kN32_SkColorType == kBGRA_8888_SkColorType ? BitConvert::BGRA : BitConvert::RGBA) std::unique_ptr<sal_uInt8[]> data - = convertDataBitCount(mBuffer.get(), mSize.Width(), mSize.Height(), mBitCount, - mScanlineSize, mPalette, GET_FORMAT); + = convertDataBitCount(mBuffer.get(), mPixelsSize.Width(), mPixelsSize.Height(), + mBitCount, mScanlineSize, mPalette, GET_FORMAT); #undef GET_FORMAT if (!bitmap.installPixels( - SkImageInfo::MakeS32(mSize.Width(), mSize.Height(), kOpaque_SkAlphaType), - data.release(), mSize.Width() * 4, + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), + kOpaque_SkAlphaType), + data.release(), mPixelsSize.Width() * 4, [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) abort(); bitmap.setImmutable(); @@ -429,7 +439,28 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage() const assert(mWriteAccessCount == 0); #endif if (mImage) + { + if (mImage->width() != mSize.Width() || mImage->height() != mSize.Height()) + { + assert(!mBuffer); // This code should be only called if only mImage holds data. + SkiaZone zone; + sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mSize); + assert(surface); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + paint.setFilterQuality(mScaleQuality); + surface->getCanvas()->drawImageRect( + mImage, SkRect::MakeWH(mImage->width(), mImage->height()), + SkRect::MakeWH(mSize.Width(), mSize.Height()), &paint); + SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): image scaled " + << Size(mImage->width(), mImage->height()) + << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mImage = surface->makeImageSnapshot(); + } return mImage; + } SkiaZone zone; sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mSize); assert(surface); @@ -447,10 +478,14 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const assert(mWriteAccessCount == 0); #endif if (mAlphaImage) + { + assert(mSize == mPixelsSize); // data has already been scaled if needed return mAlphaImage; + } SkiaZone zone; // TODO can we convert directly mImage -> mAlphaImage? EnsureBitmapData(); + assert(mSize == mPixelsSize); // data has already been scaled if needed SkBitmap alphaBitmap; if (mBuffer && mBitCount <= 8) { @@ -512,7 +547,49 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const void SkiaSalBitmap::EnsureBitmapData() { if (mBuffer) + { + if (mSize != mPixelsSize) // pending scaling? + { + // This will be pixel->pixel scaling, use VCL algorithm, it should be faster than Skia + // (no need to possibly convert bpp, it's multithreaded,...). + std::shared_ptr<SkiaSalBitmap> src = std::make_shared<SkiaSalBitmap>(); + if (!src->Create(*this)) + abort(); + // force 'src' to use VCL's scaling + src->mDisableScale = true; + src->mSize = src->mPixelsSize; + Bitmap bitmap(src); + BmpScaleFlag scaleFlag; + switch (mScaleQuality) + { + case kNone_SkFilterQuality: + scaleFlag = BmpScaleFlag::Fast; + break; + case kMedium_SkFilterQuality: + scaleFlag = BmpScaleFlag::Default; + break; + case kHigh_SkFilterQuality: + scaleFlag = BmpScaleFlag::BestQuality; + break; + default: + abort(); + } + bitmap.Scale(mSize, scaleFlag); + assert(dynamic_cast<const SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get())); + const SkiaSalBitmap* dest + = static_cast<const SkiaSalBitmap*>(bitmap.ImplGetSalBitmap().get()); + assert(dest->mSize == dest->mPixelsSize); + assert(dest->mSize == mSize); + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): pixels scaled " + << mPixelsSize << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + Destroy(); + Create(*dest); + mDisableScale = false; + } return; + } + // Try to fill mBuffer from mImage. if (!mImage) return; SkiaZone zone; @@ -529,7 +606,20 @@ void SkiaSalBitmap::EnsureBitmapData() SkCanvas canvas(mBitmap); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha - canvas.drawImage(mImage, 0, 0, &paint); + if (mSize != mPixelsSize) // pending scaling? + { + paint.setFilterQuality(mScaleQuality); + canvas.drawImageRect(mImage, + SkRect::MakeWH(mPixelsSize.getWidth(), mPixelsSize.getHeight()), + SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()), &paint); + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): image scaled " << mPixelsSize + << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + mPixelsSize = mSize; + mScaleQuality = kNone_SkFilterQuality; + } + else + canvas.drawImage(mImage, 0, 0, &paint); canvas.flush(); mBitmap.setImmutable(); assert(mBuffer != nullptr); @@ -600,11 +690,23 @@ void SkiaSalBitmap::EnsureBitmapUniqueData() void SkiaSalBitmap::ResetCachedData() { SkiaZone zone; + // There may be a case when only mImage is set and CreatBitmapData() will create + // mBuffer from it if needed, in that case ResetToSkImage() should be used. + assert(mBuffer.get() || !mImage); mBitmap.reset(); mAlphaImage.reset(); mImage.reset(); } +void SkiaSalBitmap::ResetToSkImage(sk_sp<SkImage> image) +{ + SkiaZone zone; + mBuffer.reset(); + mBitmap.reset(); + mAlphaImage.reset(); + mImage = image; +} + #ifdef DBG_UTIL void SkiaSalBitmap::dump(const char* file) const { @@ -613,6 +715,7 @@ void SkiaSalBitmap::dump(const char* file) const SkBitmap saveBitmap = mBitmap; bool resetBuffer = !mBuffer; int saveWriteAccessCount = mWriteAccessCount; + Size savePrescaleSize = mPixelsSize; SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); // avoid possible assert thisPtr->mWriteAccessCount = 0; @@ -624,13 +727,15 @@ void SkiaSalBitmap::dump(const char* file) const thisPtr->mImage = saveImage; thisPtr->mAlphaImage = saveAlphaImage; thisPtr->mWriteAccessCount = saveWriteAccessCount; + thisPtr->mPixelsSize = savePrescaleSize; } void SkiaSalBitmap::verify() const { if (!mBuffer) return; - size_t canary = mScanlineSize * mSize.Height(); + // Use mPixelsSize, that describes the size of the actual data. + size_t canary = mScanlineSize * mPixelsSize.Height(); assert(memcmp(mBuffer.get() + canary, CANARY, sizeof(CANARY)) == 0); } _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits