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"); }
SkISize onISize() override { return {1290, 990}; }
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;
}
SkISize onISize() override { return {1130, 970}; }
void onOnceBeforeDraw() override {
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);
const SkSamplingMode samplings[] = {
@ -357,11 +366,26 @@ class ShowMipLevels3 : public skiagm::GM {
for (auto sa : samplings) {
canvas->translate(0, draw_downscaling(canvas, {sa, mm}));
}
canvas->translate(0, 10);
}
return DrawResult::kOk;
}
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;
};
DEF_GM( return new ShowMipLevels3; )

View File

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

View File

@ -26,7 +26,7 @@ class SkData;
class SkCanvas;
class SkImageFilter;
class SkImageGenerator;
class SkM44;
class SkMipMap;
class SkPaint;
class SkPicture;
class SkSurface;
@ -55,6 +55,20 @@ struct SkFilterOptions {
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
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,
@ -1184,6 +1198,20 @@ public:
*/
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
compatible with SkSurface created with dstColorSpace. The returned SkImage respects
mipMapped setting; if mipMapped equals GrMipMapped::kYes, the backing texture

View File

@ -9,6 +9,7 @@
#define SkSurface_DEFINED
#include "include/core/SkImage.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSurfaceProps.h"
@ -70,6 +71,11 @@ public:
size_t rowBytes,
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.
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/SkMask.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMipMap.h"
#include "src/core/SkPixelRefPriv.h"
#include "src/core/SkPixmapPriv.h"
#include "src/core/SkReadBuffer.h"
@ -42,6 +43,7 @@ SkBitmap::SkBitmap() {}
SkBitmap::SkBitmap(const SkBitmap& src)
: fPixelRef (src.fPixelRef)
, fPixmap (src.fPixmap)
, fMips (src.fMips)
{
SkDEBUGCODE(src.validate();)
SkDEBUGCODE(this->validate();)
@ -50,6 +52,7 @@ SkBitmap::SkBitmap(const SkBitmap& src)
SkBitmap::SkBitmap(SkBitmap&& other)
: fPixelRef (std::move(other.fPixelRef))
, fPixmap (std::move(other.fPixmap))
, fMips (std::move(other.fMips))
{
SkASSERT(!other.fPixelRef);
other.fPixmap.reset();
@ -61,6 +64,7 @@ SkBitmap& SkBitmap::operator=(const SkBitmap& src) {
if (this != &src) {
fPixelRef = src.fPixelRef;
fPixmap = src.fPixmap;
fMips = src.fMips;
}
SkDEBUGCODE(this->validate();)
return *this;
@ -70,6 +74,7 @@ SkBitmap& SkBitmap::operator=(SkBitmap&& other) {
if (this != &other) {
fPixelRef = std::move(other.fPixelRef);
fPixmap = std::move(other.fPixmap);
fMips = std::move(other.fMips);
SkASSERT(!other.fPixelRef);
other.fPixmap.reset();
}
@ -85,6 +90,7 @@ void SkBitmap::swap(SkBitmap& other) {
void SkBitmap::reset() {
fPixelRef = nullptr; // Free pixels.
fPixmap.reset();
fMips.reset();
}
void SkBitmap::getBounds(SkRect* bounds) const {

View File

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

View File

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

View File

@ -29,7 +29,11 @@ typedef SkDiscardableMemory* (*SkDiscardableFactoryProc)(size_t bytes);
*/
class SkMipMap : public SkCachedData {
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);
// 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/SkImageFilter_Base.h"
#include "src/core/SkImagePriv.h"
#include "src/core/SkMipMap.h"
#include "src/core/SkNextID.h"
#include "src/core/SkSpecialImage.h"
#include "src/image/SkImage_Base.h"
@ -649,3 +650,41 @@ SkIRect SkImage_getSubset(const SkImage* image) {
SkASSERT(image);
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/SkSurface.h"
#include "src/core/SkMipMap.h"
#include <atomic>
#if SK_SUPPORT_GPU
@ -46,6 +47,12 @@ public:
virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
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.
*/
@ -128,6 +135,11 @@ public:
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:
SkImage_Base(const SkImageInfo& info, uint32_t uniqueID);

View File

@ -123,6 +123,18 @@ public:
void onUnpinAsTexture(GrContext*) const override;
#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:
SkBitmap fBitmap;

View File

@ -9,6 +9,7 @@
#include "include/utils/SkRandom.h"
#include "src/core/SkMipMap.h"
#include "tests/Test.h"
#include "tools/Resources.h"
static void make_bitmap(SkBitmap* bm, int width, int height) {
bm->allocN32Pixels(width, height);
@ -209,3 +210,29 @@ DEF_TEST(MipMap_F16, reporter) {
bmp.eraseColor(0);
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());
}