Create mipmaps when creating images

Follow-on CLs:
- add serialization for mips to pictures
- implement for other image types (e.g. lazy(?), gpu)
- improve generated mip quality

Bug: skia:10411
Change-Id: Id874f170e9cb8ae4405dc4f6249e1ea6274f20aa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297739
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Mike Reed 2020-07-14 11:29:38 -04:00 committed by Skia Commit-Bot
parent 12c5d29ba6
commit 2fe1569298
12 changed files with 212 additions and 44 deletions

View File

@ -320,29 +320,38 @@ class ShowMipLevels3 : public skiagm::GM {
SkString onShortName() override { return SkString("showmiplevels_explicit"); } SkString onShortName() override { return SkString("showmiplevels_explicit"); }
SkISize onISize() override { return {1290, 990}; } SkISize onISize() override { return {1130, 970}; }
SkScalar draw_downscaling(SkCanvas* canvas, SkFilterOptions options) {
SkAutoCanvasRestore acr(canvas, true);
SkPaint paint;
SkRect r = {0, 0, 150, 150};
for (float scale = 1; scale >= 0.125f; scale *= 0.75f) {
SkMatrix matrix = SkMatrix::Scale(scale, scale);
paint.setShader(fImg->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
options, &matrix));
canvas->drawRect(r, paint);
// r.offset(r.width() + 10, 0);
canvas->translate(r.width() + 10, 0);
}
return 160;
}
void onOnceBeforeDraw() override { void onOnceBeforeDraw() override {
fImg = GetResourceAsImage("images/ship.png"); fImg = GetResourceAsImage("images/ship.png");
fImg = fImg->makeRasterImage(); // makeWithMips only works on raster for now
const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
SkMipmapBuilder builder(fImg->imageInfo());
for (int i = 0; i < builder.countLevels(); ++i) {
auto surf = SkSurface::MakeRasterDirect(builder.level(i));
surf->getCanvas()->drawColor(colors[i % SK_ARRAY_COUNT(colors)]);
}
fImg = fImg->withMipmaps(builder.detach());
} }
void onDraw(SkCanvas* canvas) override { DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
auto is_recording_canvas = [](SkCanvas* canvas) {
SkPixmap pm;
if (canvas->peekPixels(&pm)) {
return false;
}
if (canvas->getGrContext()) {
return false;
}
return true;
};
if (is_recording_canvas(canvas)) {
// TODO: make explicit mipmaps serialize
return DrawResult::kSkip;
}
canvas->drawColor(0xFFDDDDDD); canvas->drawColor(0xFFDDDDDD);
const SkSamplingMode samplings[] = { const SkSamplingMode samplings[] = {
@ -357,11 +366,26 @@ class ShowMipLevels3 : public skiagm::GM {
for (auto sa : samplings) { for (auto sa : samplings) {
canvas->translate(0, draw_downscaling(canvas, {sa, mm})); canvas->translate(0, draw_downscaling(canvas, {sa, mm}));
} }
canvas->translate(0, 10);
} }
return DrawResult::kOk;
} }
private: private:
SkScalar draw_downscaling(SkCanvas* canvas, SkFilterOptions options) {
SkAutoCanvasRestore acr(canvas, true);
SkPaint paint;
SkRect r = {0, 0, 150, 150};
for (float scale = 1; scale >= 0.1f; scale *= 0.7f) {
SkMatrix matrix = SkMatrix::Scale(scale, scale);
paint.setShader(fImg->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
options, &matrix));
canvas->drawRect(r, paint);
canvas->translate(r.width() + 10, 0);
}
return r.height() + 10;
}
typedef skiagm::GM INHERITED; typedef skiagm::GM INHERITED;
}; };
DEF_GM( return new ShowMipLevels3; ) DEF_GM( return new ShowMipLevels3; )

View File

@ -17,6 +17,7 @@
#include "include/core/SkTileMode.h" #include "include/core/SkTileMode.h"
struct SkMask; struct SkMask;
class SkMipMap;
struct SkIRect; struct SkIRect;
struct SkRect; struct SkRect;
class SkPaint; class SkPaint;
@ -1165,7 +1166,9 @@ public:
private: private:
sk_sp<SkPixelRef> fPixelRef; sk_sp<SkPixelRef> fPixelRef;
SkPixmap fPixmap; SkPixmap fPixmap;
sk_sp<SkMipMap> fMips;
friend class SkImage_Raster;
friend class SkReadBuffer; // unflatten friend class SkReadBuffer; // unflatten
}; };

View File

@ -26,7 +26,7 @@ class SkData;
class SkCanvas; class SkCanvas;
class SkImageFilter; class SkImageFilter;
class SkImageGenerator; class SkImageGenerator;
class SkM44; class SkMipMap;
class SkPaint; class SkPaint;
class SkPicture; class SkPicture;
class SkSurface; class SkSurface;
@ -55,6 +55,20 @@ struct SkFilterOptions {
SkMipmapMode fMipmap; SkMipmapMode fMipmap;
}; };
class SkMipmapBuilder {
public:
SkMipmapBuilder(const SkImageInfo&);
~SkMipmapBuilder();
int countLevels() const;
SkPixmap level(int index) const;
sk_sp<SkMipMap> detach();
private:
sk_sp<SkMipMap> fMM;
};
/** \class SkImage /** \class SkImage
SkImage describes a two dimensional array of pixels to draw. The pixels may be SkImage describes a two dimensional array of pixels to draw. The pixels may be
decoded in a raster bitmap, encoded in a SkPicture or compressed data stream, decoded in a raster bitmap, encoded in a SkPicture or compressed data stream,
@ -1184,6 +1198,20 @@ public:
*/ */
sk_sp<SkImage> makeSubset(const SkIRect& subset, GrDirectContext* direct = nullptr) const; sk_sp<SkImage> makeSubset(const SkIRect& subset, GrDirectContext* direct = nullptr) const;
/**
* Returns true if the image has mipmap levels.
*/
bool hasMipmaps() const;
/**
* Returns an image with the same "base" pixels as the this image, but with mipmap levels
* as well. If this image already has mipmap levels, they will be replaced with new ones.
*
* If data == nullptr, the mipmap levels are computed automatically.
* If data != nullptr, then the caller has provided the data for each level.
*/
sk_sp<SkImage> withMipmaps(sk_sp<SkMipMap> data) const;
/** Returns SkImage backed by GPU texture associated with context. Returned SkImage is /** Returns SkImage backed by GPU texture associated with context. Returned SkImage is
compatible with SkSurface created with dstColorSpace. The returned SkImage respects compatible with SkSurface created with dstColorSpace. The returned SkImage respects
mipMapped setting; if mipMapped equals GrMipMapped::kYes, the backing texture mipMapped setting; if mipMapped equals GrMipMapped::kYes, the backing texture

View File

@ -9,6 +9,7 @@
#define SkSurface_DEFINED #define SkSurface_DEFINED
#include "include/core/SkImage.h" #include "include/core/SkImage.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRefCnt.h" #include "include/core/SkRefCnt.h"
#include "include/core/SkSurfaceProps.h" #include "include/core/SkSurfaceProps.h"
@ -70,6 +71,11 @@ public:
size_t rowBytes, size_t rowBytes,
const SkSurfaceProps* surfaceProps = nullptr); const SkSurfaceProps* surfaceProps = nullptr);
static sk_sp<SkSurface> MakeRasterDirect(const SkPixmap& pm,
const SkSurfaceProps* props = nullptr) {
return MakeRasterDirect(pm.info(), pm.writable_addr(), pm.rowBytes(), props);
}
/** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels. /** Allocates raster SkSurface. SkCanvas returned by SkSurface draws directly into pixels.
releaseProc is called with pixels and context when SkSurface is deleted. releaseProc is called with pixels and context when SkSurface is deleted.

View File

@ -23,6 +23,7 @@
#include "src/core/SkConvertPixels.h" #include "src/core/SkConvertPixels.h"
#include "src/core/SkMask.h" #include "src/core/SkMask.h"
#include "src/core/SkMaskFilterBase.h" #include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMipMap.h"
#include "src/core/SkPixelRefPriv.h" #include "src/core/SkPixelRefPriv.h"
#include "src/core/SkPixmapPriv.h" #include "src/core/SkPixmapPriv.h"
#include "src/core/SkReadBuffer.h" #include "src/core/SkReadBuffer.h"
@ -42,6 +43,7 @@ SkBitmap::SkBitmap() {}
SkBitmap::SkBitmap(const SkBitmap& src) SkBitmap::SkBitmap(const SkBitmap& src)
: fPixelRef (src.fPixelRef) : fPixelRef (src.fPixelRef)
, fPixmap (src.fPixmap) , fPixmap (src.fPixmap)
, fMips (src.fMips)
{ {
SkDEBUGCODE(src.validate();) SkDEBUGCODE(src.validate();)
SkDEBUGCODE(this->validate();) SkDEBUGCODE(this->validate();)
@ -50,6 +52,7 @@ SkBitmap::SkBitmap(const SkBitmap& src)
SkBitmap::SkBitmap(SkBitmap&& other) SkBitmap::SkBitmap(SkBitmap&& other)
: fPixelRef (std::move(other.fPixelRef)) : fPixelRef (std::move(other.fPixelRef))
, fPixmap (std::move(other.fPixmap)) , fPixmap (std::move(other.fPixmap))
, fMips (std::move(other.fMips))
{ {
SkASSERT(!other.fPixelRef); SkASSERT(!other.fPixelRef);
other.fPixmap.reset(); other.fPixmap.reset();
@ -61,6 +64,7 @@ SkBitmap& SkBitmap::operator=(const SkBitmap& src) {
if (this != &src) { if (this != &src) {
fPixelRef = src.fPixelRef; fPixelRef = src.fPixelRef;
fPixmap = src.fPixmap; fPixmap = src.fPixmap;
fMips = src.fMips;
} }
SkDEBUGCODE(this->validate();) SkDEBUGCODE(this->validate();)
return *this; return *this;
@ -70,6 +74,7 @@ SkBitmap& SkBitmap::operator=(SkBitmap&& other) {
if (this != &other) { if (this != &other) {
fPixelRef = std::move(other.fPixelRef); fPixelRef = std::move(other.fPixelRef);
fPixmap = std::move(other.fPixmap); fPixmap = std::move(other.fPixmap);
fMips = std::move(other.fMips);
SkASSERT(!other.fPixelRef); SkASSERT(!other.fPixelRef);
other.fPixmap.reset(); other.fPixmap.reset();
} }
@ -85,6 +90,7 @@ void SkBitmap::swap(SkBitmap& other) {
void SkBitmap::reset() { void SkBitmap::reset() {
fPixelRef = nullptr; // Free pixels. fPixelRef = nullptr; // Free pixels.
fPixmap.reset(); fPixmap.reset();
fMips.reset();
} }
void SkBitmap::getBounds(SkRect* bounds) const { void SkBitmap::getBounds(SkRect* bounds) const {

View File

@ -15,6 +15,18 @@
#include "src/core/SkMipMap.h" #include "src/core/SkMipMap.h"
#include "src/image/SkImage_Base.h" #include "src/image/SkImage_Base.h"
// Try to load from the base image, or from the cache
static sk_sp<const SkMipMap> try_load_mips(const SkImage_Base* image) {
sk_sp<const SkMipMap> mips = image->refMips();
if (!mips) {
mips.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image)));
}
if (!mips) {
mips.reset(SkMipMapCache::AddAndRef(image));
}
return mips;
}
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
SkBitmapController::State* SkBitmapController::RequestBitmap(const SkImage_Base* image, SkBitmapController::State* SkBitmapController::RequestBitmap(const SkImage_Base* image,
@ -60,12 +72,9 @@ bool SkBitmapController::State::processMediumRequest(const SkImage_Base* image)
} }
if (invScaleSize.width() > SK_Scalar1 || invScaleSize.height() > SK_Scalar1) { if (invScaleSize.width() > SK_Scalar1 || invScaleSize.height() > SK_Scalar1) {
fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image))); fCurrMip = try_load_mips(image);
if (nullptr == fCurrMip.get()) { if (!fCurrMip) {
fCurrMip.reset(SkMipMapCache::AddAndRef(image)); return false;
if (nullptr == fCurrMip.get()) {
return false;
}
} }
// diagnostic for a crasher... // diagnostic for a crasher...
SkASSERT_RELEASE(fCurrMip->data()); SkASSERT_RELEASE(fCurrMip->data());
@ -143,16 +152,11 @@ SkMipmapAccessor::SkMipmapAccessor(const SkImage_Base* image, const SkMatrix& in
} }
// load fCurrMip if needed // load fCurrMip if needed
if (levelNum > 0 || (fResolvedMode == SkMipmapMode::kLinear && lowerWeight > 0)) { if (levelNum > 0 || (fResolvedMode == SkMipmapMode::kLinear && lowerWeight > 0)) {
// try to load from the cache fCurrMip = try_load_mips(image);
fCurrMip.reset(SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(image)));
if (!fCurrMip) { if (!fCurrMip) {
fCurrMip.reset(SkMipMapCache::AddAndRef(image)); load_upper_from_base();
if (!fCurrMip) { fResolvedMode = SkMipmapMode::kNone;
load_upper_from_base(); } else {
fResolvedMode = SkMipmapMode::kNone;
}
}
if (fCurrMip) {
SkMipMap::Level levelRec; SkMipMap::Level levelRec;
SkASSERT(fResolvedMode != SkMipmapMode::kNone); SkASSERT(fResolvedMode != SkMipmapMode::kNone);

View File

@ -395,7 +395,8 @@ size_t SkMipMap::AllocLevelsSize(int levelCount, size_t pixelSize) {
return SkTo<int32_t>(size); return SkTo<int32_t>(size);
} }
SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) { SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact,
bool computeContents) {
typedef void FilterProc(void*, const void* srcPtr, size_t srcRB, int count); typedef void FilterProc(void*, const void* srcPtr, size_t srcRB, int count);
FilterProc* proc_1_2 = nullptr; FilterProc* proc_1_2 = nullptr;
@ -632,14 +633,16 @@ SkMipMap* SkMipMap::Build(const SkPixmap& src, SkDiscardableFactoryProc fact) {
SkIntToScalar(height) / src.height()); SkIntToScalar(height) / src.height());
const SkPixmap& dstPM = levels[i].fPixmap; const SkPixmap& dstPM = levels[i].fPixmap;
const void* srcBasePtr = srcPM.addr(); if (computeContents) {
void* dstBasePtr = dstPM.writable_addr(); const void* srcBasePtr = srcPM.addr();
void* dstBasePtr = dstPM.writable_addr();
const size_t srcRB = srcPM.rowBytes(); const size_t srcRB = srcPM.rowBytes();
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
proc(dstBasePtr, srcBasePtr, srcRB, width); proc(dstBasePtr, srcBasePtr, srcRB, width);
srcBasePtr = (char*)srcBasePtr + srcRB * 2; // jump two rows srcBasePtr = (char*)srcBasePtr + srcRB * 2; // jump two rows
dstBasePtr = (char*)dstBasePtr + dstPM.rowBytes(); dstBasePtr = (char*)dstBasePtr + dstPM.rowBytes();
}
} }
srcPM = dstPM; srcPM = dstPM;
addr += height * rowBytes; addr += height * rowBytes;

View File

@ -29,7 +29,11 @@ typedef SkDiscardableMemory* (*SkDiscardableFactoryProc)(size_t bytes);
*/ */
class SkMipMap : public SkCachedData { class SkMipMap : public SkCachedData {
public: public:
static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc); // Allocate and fill-in a mipmap. If computeContents is false, we just allocated
// and compute the sizes/rowbytes, but leave the pixel-data uninitialized.
static SkMipMap* Build(const SkPixmap& src, SkDiscardableFactoryProc,
bool computeContents = true);
static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc); static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc);
// Determines how many levels a SkMipMap will have without creating that mipmap. // Determines how many levels a SkMipMap will have without creating that mipmap.

View File

@ -20,6 +20,7 @@
#include "src/core/SkImageFilterCache.h" #include "src/core/SkImageFilterCache.h"
#include "src/core/SkImageFilter_Base.h" #include "src/core/SkImageFilter_Base.h"
#include "src/core/SkImagePriv.h" #include "src/core/SkImagePriv.h"
#include "src/core/SkMipMap.h"
#include "src/core/SkNextID.h" #include "src/core/SkNextID.h"
#include "src/core/SkSpecialImage.h" #include "src/core/SkSpecialImage.h"
#include "src/image/SkImage_Base.h" #include "src/image/SkImage_Base.h"
@ -649,3 +650,41 @@ SkIRect SkImage_getSubset(const SkImage* image) {
SkASSERT(image); SkASSERT(image);
return as_IB(image)->onGetSubset(); return as_IB(image)->onGetSubset();
} }
///////////////////////////////////////////////////////////////////////////////////////////////////
SkMipmapBuilder::SkMipmapBuilder(const SkImageInfo& info) {
fMM = sk_sp<SkMipMap>(SkMipMap::Build({info, nullptr, 0}, nullptr, false));
}
SkMipmapBuilder::~SkMipmapBuilder() {}
int SkMipmapBuilder::countLevels() const {
return fMM ? fMM->countLevels() : 0;
}
SkPixmap SkMipmapBuilder::level(int index) const {
SkPixmap pm;
SkMipMap::Level level;
if (fMM && fMM->getLevel(index, &level)) {
pm = level.fPixmap;
}
return pm;
}
sk_sp<SkMipMap> SkMipmapBuilder::detach() {
return std::move(fMM);
}
bool SkImage::hasMipmaps() const {
return as_IB(this)->onPeekMips() != nullptr;
}
sk_sp<SkImage> SkImage::withMipmaps(sk_sp<SkMipMap> data) const {
auto result = as_IB(this)->onMakeWithMipmaps(std::move(data));
if (!result) {
result = sk_ref_sp((const_cast<SkImage*>(this)));
}
return result;
}

View File

@ -10,6 +10,7 @@
#include "include/core/SkImage.h" #include "include/core/SkImage.h"
#include "include/core/SkSurface.h" #include "include/core/SkSurface.h"
#include "src/core/SkMipMap.h"
#include <atomic> #include <atomic>
#if SK_SUPPORT_GPU #if SK_SUPPORT_GPU
@ -46,6 +47,12 @@ public:
virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
int srcX, int srcY, CachingHint) const = 0; int srcX, int srcY, CachingHint) const = 0;
virtual SkMipMap* onPeekMips() const { return nullptr; }
sk_sp<SkMipMap> refMips() const {
return sk_ref_sp(this->onPeekMips());
}
/** /**
* Default implementation does a rescale/read and then calls the callback. * Default implementation does a rescale/read and then calls the callback.
*/ */
@ -128,6 +135,11 @@ public:
virtual sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const = 0; virtual sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const = 0;
// on failure, returns nullptr
virtual sk_sp<SkImage> onMakeWithMipmaps(sk_sp<SkMipMap>) const {
return nullptr;
}
protected: protected:
SkImage_Base(const SkImageInfo& info, uint32_t uniqueID); SkImage_Base(const SkImageInfo& info, uint32_t uniqueID);

View File

@ -123,6 +123,18 @@ public:
void onUnpinAsTexture(GrContext*) const override; void onUnpinAsTexture(GrContext*) const override;
#endif #endif
SkMipMap* onPeekMips() const override { return fBitmap.fMips.get(); }
sk_sp<SkImage> onMakeWithMipmaps(sk_sp<SkMipMap> mips) const override {
auto img = new SkImage_Raster(fBitmap);
if (mips) {
img->fBitmap.fMips = std::move(mips);
} else {
img->fBitmap.fMips.reset(SkMipMap::Build(fBitmap.pixmap(), nullptr));
}
return sk_sp<SkImage>(img);
}
private: private:
SkBitmap fBitmap; SkBitmap fBitmap;

View File

@ -9,6 +9,7 @@
#include "include/utils/SkRandom.h" #include "include/utils/SkRandom.h"
#include "src/core/SkMipMap.h" #include "src/core/SkMipMap.h"
#include "tests/Test.h" #include "tests/Test.h"
#include "tools/Resources.h"
static void make_bitmap(SkBitmap* bm, int width, int height) { static void make_bitmap(SkBitmap* bm, int width, int height) {
bm->allocN32Pixels(width, height); bm->allocN32Pixels(width, height);
@ -209,3 +210,29 @@ DEF_TEST(MipMap_F16, reporter) {
bmp.eraseColor(0); bmp.eraseColor(0);
sk_sp<SkMipMap> mipmap(SkMipMap::Build(bmp, nullptr)); sk_sp<SkMipMap> mipmap(SkMipMap::Build(bmp, nullptr));
} }
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
DEF_TEST(image_mip_factory, reporter) {
// TODO: what do to about lazy images and mipmaps?
auto img = GetResourceAsImage("images/mandrill_128.png")->makeRasterImage();
REPORTER_ASSERT(reporter, !img->hasMipmaps());
auto img1 = img->withMipmaps(nullptr);
REPORTER_ASSERT(reporter, img.get() != img1.get());
REPORTER_ASSERT(reporter, img1->hasMipmaps());
SkMipmapBuilder builder(img->imageInfo());
int count = builder.countLevels();
for (int i = 0; i < count; ++i) {
SkPixmap pm = builder.level(i);
auto surf = SkSurface::MakeRasterDirect(pm);
surf->getCanvas()->drawImageRect(img, SkRect::MakeIWH(pm.width(), pm.height()), nullptr);
}
auto img2 = img->withMipmaps(builder.detach());
REPORTER_ASSERT(reporter, !builder.detach());
REPORTER_ASSERT(reporter, img.get() != img2.get());
REPORTER_ASSERT(reporter, img1.get() != img2.get());
REPORTER_ASSERT(reporter, img2->hasMipmaps());
}