Store mipmap levels in deferred texture image
This is a follow-up to https://codereview.chromium.org/2115023002/ and
https://codereview.chromium.org/2034933003/ which were reverted due to
an access violation and a memory leak, respectively.
When creating the deferred texture image, detect if using medium / high
quality. If so, generate and store mipmaps in the deferred texture
image.
When creating a texture from that be sure to read it back out.
BUG=578304
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2242883004
Review-Url: https://codereview.chromium.org/2242883004
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 53765b1..06ec224 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -5,11 +5,16 @@
* found in the LICENSE file.
*/
+#include <cstddef>
+#include <cstring>
+#include <type_traits>
+
#include "SkAutoPixmapStorage.h"
#include "GrCaps.h"
#include "GrContext.h"
#include "GrDrawContext.h"
#include "GrImageIDTextureAdjuster.h"
+#include "GrTexturePriv.h"
#include "effects/GrYUVEffect.h"
#include "SkCanvas.h"
#include "SkBitmapCache.h"
@@ -358,35 +363,84 @@
};
struct DeferredTextureImage {
- uint32_t fContextUniqueID;
+ uint32_t fContextUniqueID;
+ // Right now, the gamma treatment is only considered when generating mipmaps
+ SkSourceGammaTreatment fGammaTreatment;
// We don't store a SkImageInfo because it contains a ref-counted SkColorSpace.
- int fWidth;
- int fHeight;
- SkColorType fColorType;
- SkAlphaType fAlphaType;
- void* fColorSpace;
- size_t fColorSpaceSize;
- int fColorTableCnt;
- uint32_t* fColorTableData;
- int fMipMapLevelCount;
+ int fWidth;
+ int fHeight;
+ SkColorType fColorType;
+ SkAlphaType fAlphaType;
+ void* fColorSpace;
+ size_t fColorSpaceSize;
+ int fColorTableCnt;
+ uint32_t* fColorTableData;
+ int fMipMapLevelCount;
// The fMipMapLevelData array may contain more than 1 element.
// It contains fMipMapLevelCount elements.
// That means this struct's size is not known at compile-time.
- MipMapLevelData fMipMapLevelData[1];
+ MipMapLevelData fMipMapLevelData[1];
};
} // anonymous namespace
+static bool should_use_mip_maps(const SkImage::DeferredTextureImageUsageParams & param) {
+ bool shouldUseMipMaps = false;
+
+ // Use mipmaps if either
+ // 1.) it is a perspective matrix, or
+ // 2.) the quality is med/high and the scale is < 1
+ if (param.fMatrix.hasPerspective()) {
+ shouldUseMipMaps = true;
+ }
+ if (param.fQuality == kMedium_SkFilterQuality ||
+ param.fQuality == kHigh_SkFilterQuality) {
+ SkScalar minAxisScale = param.fMatrix.getMinScale();
+ if (minAxisScale != -1.f && minAxisScale < 1.f) {
+ shouldUseMipMaps = true;
+ }
+ }
+
+
+ return shouldUseMipMaps;
+}
+
+namespace {
+
+class DTIBufferFiller
+{
+public:
+ explicit DTIBufferFiller(intptr_t bufferAsInt)
+ : bufferAsInt_(bufferAsInt) {}
+
+ void fillMember(const void* source, size_t memberOffset, size_t size) {
+ memcpy(reinterpret_cast<void*>(bufferAsInt_ + memberOffset), source, size);
+ }
+
+private:
+
+ intptr_t bufferAsInt_;
+};
+}
+
+#define FILL_MEMBER(bufferFiller, member, source) \
+ bufferFiller.fillMember(source, \
+ offsetof(DeferredTextureImage, member), \
+ sizeof(DeferredTextureImage::member));
+
size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
const DeferredTextureImageUsageParams params[],
- int paramCnt, void* buffer) const {
+ int paramCnt, void* buffer,
+ SkSourceGammaTreatment gammaTreatment) const {
// Extract relevant min/max values from the params array.
int lowestPreScaleMipLevel = params[0].fPreScaleMipLevel;
SkFilterQuality highestFilterQuality = params[0].fQuality;
+ bool useMipMaps = should_use_mip_maps(params[0]);
for (int i = 1; i < paramCnt; ++i) {
if (lowestPreScaleMipLevel > params[i].fPreScaleMipLevel)
lowestPreScaleMipLevel = params[i].fPreScaleMipLevel;
if (highestFilterQuality < params[i].fQuality)
highestFilterQuality = params[i].fQuality;
+ useMipMaps |= should_use_mip_maps(params[i]);
}
const bool fillMode = SkToBool(buffer);
@@ -455,11 +509,35 @@
SkASSERT(!pixmap.ctable());
}
}
+ SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
int mipMapLevelCount = 1;
+ if (useMipMaps) {
+ // SkMipMap only deals with the mipmap levels it generates, which does
+ // not include the base level.
+ // That means it generates and holds levels 1-x instead of 0-x.
+ // So the total mipmap level count is 1 more than what
+ // SkMipMap::ComputeLevelCount returns.
+ mipMapLevelCount = SkMipMap::ComputeLevelCount(scaledSize.width(), scaledSize.height()) + 1;
+
+ // We already initialized pixelSize to the size of the base level.
+ // SkMipMap will generate the extra mipmap levels. Their sizes need to
+ // be added to the total.
+ // Index 0 here does not refer to the base mipmap level -- it is
+ // SkMipMap's first generated mipmap level (level 1).
+ for (int currentMipMapLevelIndex = mipMapLevelCount - 2; currentMipMapLevelIndex >= 0;
+ currentMipMapLevelIndex--) {
+ SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
+ currentMipMapLevelIndex);
+ SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
+ pixelSize += SkAlign8(SkAutoPixmapStorage::AllocSize(mipInfo, nullptr));
+ }
+ }
size_t size = 0;
size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
size += dtiSize;
- size += mipMapLevelCount * sizeof(MipMapLevelData);
+ size += (mipMapLevelCount - 1) * sizeof(MipMapLevelData);
+ // We subtract 1 because DeferredTextureImage already includes the base
+ // level in its size
size_t pixelOffset = size;
size += pixelSize;
size_t ctOffset = size;
@@ -475,37 +553,106 @@
return size;
}
intptr_t bufferAsInt = reinterpret_cast<intptr_t>(buffer);
- void* pixels = reinterpret_cast<void*>(bufferAsInt + pixelOffset);
- SkPMColor* ct = nullptr;
+ intptr_t pixelsAsInt = bufferAsInt + pixelOffset;
+ void* pixels = reinterpret_cast<void*>(pixelsAsInt);
+ void* ct = nullptr;
if (ctSize) {
- ct = reinterpret_cast<SkPMColor*>(bufferAsInt + ctOffset);
+ ct = reinterpret_cast<void*>(bufferAsInt + ctOffset);
}
- memcpy(pixels, pixmap.addr(), pixmap.getSafeSize());
+ memcpy(reinterpret_cast<void*>(SkAlign8(pixelsAsInt)), pixmap.addr(), pixmap.getSafeSize());
if (ctSize) {
memcpy(ct, pixmap.ctable()->readColors(), ctSize);
}
SkASSERT(info == pixmap.info());
size_t rowBytes = pixmap.rowBytes();
- DeferredTextureImage* dti = new (buffer) DeferredTextureImage();
- dti->fContextUniqueID = proxy.fContextUniqueID;
- dti->fWidth = info.width();
- dti->fHeight = info.height();
- dti->fColorType = info.colorType();
- dti->fAlphaType = info.alphaType();
- dti->fColorTableCnt = ctCount;
- dti->fColorTableData = ct;
- dti->fMipMapLevelCount = mipMapLevelCount;
- dti->fMipMapLevelData[0].fPixelData = pixels;
- dti->fMipMapLevelData[0].fRowBytes = rowBytes;
+ static_assert(std::is_standard_layout<DeferredTextureImage>::value,
+ "offsetof, which we use below, requires the type have standard layout");
+ auto dtiBufferFiller = DTIBufferFiller{bufferAsInt};
+ FILL_MEMBER(dtiBufferFiller, fGammaTreatment, &gammaTreatment);
+ FILL_MEMBER(dtiBufferFiller, fContextUniqueID, &proxy.fContextUniqueID);
+ int width = info.width();
+ FILL_MEMBER(dtiBufferFiller, fWidth, &width);
+ int height = info.height();
+ FILL_MEMBER(dtiBufferFiller, fHeight, &height);
+ SkColorType colorType = info.colorType();
+ FILL_MEMBER(dtiBufferFiller, fColorType, &colorType);
+ SkAlphaType alphaType = info.alphaType();
+ FILL_MEMBER(dtiBufferFiller, fAlphaType, &alphaType);
+ FILL_MEMBER(dtiBufferFiller, fColorTableCnt, &ctCount);
+ FILL_MEMBER(dtiBufferFiller, fColorTableData, &ct);
+ FILL_MEMBER(dtiBufferFiller, fMipMapLevelCount, &mipMapLevelCount);
+// FILL_MEMBER(dtiBufferFiller, fMipMapLevelCount[0].fPixelData, &pixels);
+ memcpy(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fMipMapLevelData[0].fPixelData)),
+ &pixels, sizeof(pixels));
+ memcpy(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fMipMapLevelData[0].fRowBytes)),
+ &rowBytes, sizeof(rowBytes));
if (colorSpaceSize) {
- dti->fColorSpace = reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset);
- dti->fColorSpaceSize = colorSpaceSize;
- info.colorSpace()->writeToMemory(dti->fColorSpace);
+ void* colorSpace = reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset);
+ FILL_MEMBER(dtiBufferFiller, fColorSpace, &colorSpace);
+ FILL_MEMBER(dtiBufferFiller, fColorSpaceSize, &colorSpaceSize);
+ info.colorSpace()->writeToMemory(reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset));
} else {
- dti->fColorSpace = nullptr;
- dti->fColorSpaceSize = 0;
+ memset(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fColorSpace)),
+ 0, sizeof(DeferredTextureImage::fColorSpace));
+ memset(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fColorSpaceSize)),
+ 0, sizeof(DeferredTextureImage::fColorSpaceSize));
+ }
+
+ // Fill in the mipmap levels if they exist
+ intptr_t mipLevelPtr = bufferAsInt + pixelOffset + SkAlign8(pixmap.getSafeSize());
+
+ if (useMipMaps) {
+ // offsetof, which we use below, requires the type have standard layout
+ SkASSERT(std::is_standard_layout<MipMapLevelData>::value);
+
+ SkAutoTDelete<SkMipMap> mipmaps(SkMipMap::Build(pixmap, gammaTreatment, nullptr));
+ // SkMipMap holds only the mipmap levels it generates.
+ // A programmer can use the data they provided to SkMipMap::Build as level 0.
+ // So the SkMipMap provides levels 1-x but it stores them in its own
+ // range 0-(x-1).
+ for (int generatedMipLevelIndex = 0; generatedMipLevelIndex < mipMapLevelCount - 1;
+ generatedMipLevelIndex++) {
+ SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
+ generatedMipLevelIndex);
+
+ SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
+ SkMipMap::Level mipLevel;
+ mipmaps->getLevel(generatedMipLevelIndex, &mipLevel);
+
+ // Make sure the mipmap data is after the start of the buffer
+ SkASSERT(mipLevelPtr > bufferAsInt);
+ // Make sure the mipmap data starts before the end of the buffer
+ SkASSERT(static_cast<size_t>(mipLevelPtr) < bufferAsInt + pixelOffset + pixelSize);
+ // Make sure the mipmap data ends before the end of the buffer
+ SkASSERT(mipLevelPtr + mipLevel.fPixmap.getSafeSize() <=
+ bufferAsInt + pixelOffset + pixelSize);
+
+ // getSafeSize includes rowbyte padding except for the last row,
+ // right?
+
+ memcpy(reinterpret_cast<void*>(mipLevelPtr), mipLevel.fPixmap.addr(),
+ mipLevel.fPixmap.getSafeSize());
+
+ memcpy(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fMipMapLevelData) +
+ sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+ offsetof(MipMapLevelData, fPixelData)),
+ &mipLevelPtr, sizeof(void*));
+ size_t rowBytes = mipLevel.fPixmap.rowBytes();
+ memcpy(reinterpret_cast<void*>(bufferAsInt +
+ offsetof(DeferredTextureImage, fMipMapLevelData) +
+ sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+ offsetof(MipMapLevelData, fRowBytes)),
+ &rowBytes, sizeof(rowBytes));
+
+ mipLevelPtr += SkAlign8(mipLevel.fPixmap.getSafeSize());
+ }
}
return size;
}
@@ -525,24 +672,38 @@
SkASSERT(dti->fColorTableData);
colorTable.reset(new SkColorTable(dti->fColorTableData, dti->fColorTableCnt));
}
- SkASSERT(dti->fMipMapLevelCount == 1);
+ int mipLevelCount = dti->fMipMapLevelCount;
+ SkASSERT(mipLevelCount >= 1);
sk_sp<SkColorSpace> colorSpace;
if (dti->fColorSpaceSize) {
colorSpace = SkColorSpace::Deserialize(dti->fColorSpace, dti->fColorSpaceSize);
}
SkImageInfo info = SkImageInfo::Make(dti->fWidth, dti->fHeight,
dti->fColorType, dti->fAlphaType, colorSpace);
- SkPixmap pixmap;
- pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
- dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
- return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
+ if (mipLevelCount == 1) {
+ SkPixmap pixmap;
+ pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
+ dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
+ return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
+ } else {
+ SkAutoTDeleteArray<GrMipLevel> texels(new GrMipLevel[mipLevelCount]);
+ for (int i = 0; i < mipLevelCount; i++) {
+ texels[i].fPixels = dti->fMipMapLevelData[i].fPixelData;
+ texels[i].fRowBytes = dti->fMipMapLevelData[i].fRowBytes;
+ }
+
+ return SkImage::MakeTextureFromMipMap(context, info, texels.get(),
+ mipLevelCount, SkBudgeted::kYes,
+ dti->fGammaTreatment);
+ }
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo& info,
const GrMipLevel* texels, int mipLevelCount,
- SkBudgeted budgeted) {
+ SkBudgeted budgeted,
+ SkSourceGammaTreatment gammaTreatment) {
if (!ctx) {
return nullptr;
}
@@ -550,6 +711,7 @@
if (!texture) {
return nullptr;
}
+ texture->texturePriv().setGammaTreatment(gammaTreatment);
return sk_make_sp<SkImage_Gpu>(texture->width(), texture->height(), kNeedNewImageUniqueID,
info.alphaType(), texture, sk_ref_sp(info.colorSpace()),
budgeted);