Add color space and type conversion support to asyncReadPixels

Change-Id: Ie95d467c0fcff75635e0b28ea0065cf0cbc7107b
Bug: skia:8962
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/213180
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2019-05-10 16:32:54 -04:00 committed by Skia Commit-Bot
parent b3f6654a54
commit cd734f6539
2 changed files with 160 additions and 48 deletions

View File

@ -15,6 +15,8 @@
#include "include/private/GrRecordingContext.h"
#include "include/private/SkShadowFlags.h"
#include "include/utils/SkShadowUtils.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkConvertPixels.h"
#include "src/core/SkDrawShadowInfo.h"
#include "src/core/SkGlyphRunPainter.h"
#include "src/core/SkLatticeIter.h"
@ -1754,24 +1756,26 @@ bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_s
if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) && at != kPremul_SkAlphaType) {
return false;
}
// TODO(bsalomon): Enhance support for reading to different color types.
auto dstCT = SkColorTypeToGrColorType(ct);
auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
if (readCT != dstCT) {
// Fail if we can't do a CPU conversion from readCT to dstCT.
if (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType) {
return false;
}
// Fail if readCT does not have all of readCT's color channels.
if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) {
return false;
}
// Fail if readCT is not supported for transfer buffers.
if (!this->caps()->transferFromOffsetAlignment(readCT)) {
return false;
}
// TODO(bsalomon): Support color space conversion.
if (!SkColorSpace::Equals(cs.get(), this->colorSpaceInfo().colorSpace())) {
return false;
}
sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(this->colorSpaceInfo().colorSpace(),
kPremul_SkAlphaType, cs.get(), at);
// Insert a draw to a temporary surface if we need to do a y-flip (and in future for a color
// space conversion.)
if (this->origin() == kBottomLeft_GrSurfaceOrigin) {
// Insert a draw to a temporary surface if we need to do a y-flip or color space conversion.
if (this->origin() == kBottomLeft_GrSurfaceOrigin || xform) {
sk_sp<GrTextureProxy> texProxy = sk_ref_sp(fRenderTargetProxy->asTextureProxy());
const auto& backendFormat = fRenderTargetProxy->backendFormat();
SkRect srcRectToDraw = SkRect::Make(srcRect);
@ -1804,21 +1808,24 @@ bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_s
SkBlendMode::kSrc, SK_PMColor4fWHITE, srcRectToDraw,
SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
/* colorSpaceXform = */ nullptr);
std::move(xform));
return rtc->asyncReadPixels(ct, at, std::move(cs),
SkIRect::MakeWH(srcRect.width(), srcRect.height()), callback,
context);
}
size_t rowBytes = GrColorTypeBytesPerPixel(dstCT) * srcRect.width();
size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * srcRect.width();
size_t size = rowBytes * srcRect.height();
auto buffer = direct->priv().resourceProvider()->createBuffer(
size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
if (!buffer) {
return false;
}
this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, srcRect, dstCT, buffer, 0),
this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, srcRect, readCT, buffer, 0),
*this->caps());
struct FinishContext {
SkImageInfo fReadInfo;
SkImageInfo fDstInfo;
ReadPixelsCallback* fClientCallback;
ReadPixelsContext fClientContext;
sk_sp<GrGpuBuffer> fBuffer;
@ -1827,11 +1834,33 @@ bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_s
// Assumption is that the caller would like to flush. We could take a parameter or require an
// explicit flush from the caller. We'd have to have a way to defer attaching the finish
// callback to GrGpu until after the next flush that flushes our op list, though.
auto* finishContext = new FinishContext{callback, context, buffer, rowBytes};
auto* finishContext =
new FinishContext{SkImageInfo::Make(srcRect.width(), srcRect.height(),
GrColorTypeToSkColorType(readCT), at, cs),
SkImageInfo::Make(srcRect.width(), srcRect.height(), ct, at, cs),
callback,
context,
buffer,
rowBytes};
auto finishCallback = [](GrGpuFinishedContext c) {
auto context = reinterpret_cast<const FinishContext*>(c);
void* data = context->fBuffer->map();
(*context->fClientCallback)(context->fClientContext, data, data ? context->fRowBytes : 0);
if (!data) {
(*context->fClientCallback)(context->fClientContext, nullptr, 0);
delete context;
return;
}
SkAutoPixmapStorage pm;
const void* callbackData = data;
size_t callbackRowBytes = context->fRowBytes;
if (context->fDstInfo != context->fReadInfo) {
pm.alloc(context->fDstInfo);
SkConvertPixels(context->fDstInfo, pm.writable_addr(), pm.rowBytes(),
context->fReadInfo, data, context->fRowBytes);
callbackData = pm.addr();
callbackRowBytes = pm.rowBytes();
}
(*context->fClientCallback)(context->fClientContext, callbackData, callbackRowBytes);
if (data) {
context->fBuffer->unmap();
}

View File

@ -13,6 +13,7 @@
#include "include/private/SkHalf.h"
#include "include/private/SkImageInfoPriv.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkConvertPixels.h"
#include "src/core/SkMathPriv.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrProxyProvider.h"
@ -688,6 +689,65 @@ DEF_TEST(ReadPixels_ValidConversion, reporter) {
}
}
namespace {
using ComparePixmapsErrorReporter = void(int x, int y, const float diffs[4]);
} // anonymous namespace
static void compare_pixmaps(const SkPixmap& a, const SkPixmap& b, const float tol[4],
std::function<ComparePixmapsErrorReporter>& error) {
if (a.width() != b.width() || a.height() != b.height()) {
static constexpr float kDummyDiffs[4] = {};
error(-1, -1, kDummyDiffs);
return;
}
SkAutoPixmapStorage afloat;
SkAutoPixmapStorage bfloat;
afloat.alloc(a.info().makeColorType(kRGBA_F32_SkColorType));
bfloat.alloc(b.info().makeColorType(kRGBA_F32_SkColorType));
SkConvertPixels(afloat.info(), afloat.writable_addr(), afloat.rowBytes(), a.info(), a.addr(),
a.rowBytes());
SkConvertPixels(bfloat.info(), bfloat.writable_addr(), bfloat.rowBytes(), b.info(), b.addr(),
b.rowBytes());
for (int y = 0; y < a.height(); ++y) {
for (int x = 0; x < a.width(); ++x) {
const float* rgbaA = static_cast<const float*>(afloat.addr(x, y));
const float* rgbaB = static_cast<const float*>(bfloat.addr(x, y));
float diffs[4];
bool bad = false;
for (int i = 0; i < 4; ++i) {
diffs[i] = rgbaB[i] - rgbaA[i];
if (std::abs(diffs[i]) > tol[i]) {
bad = true;
}
}
if (bad) {
error(x, y, diffs);
return;
}
}
}
}
static int min_rgb_channel_bits(SkColorType ct) {
switch (ct) {
case kUnknown_SkColorType: return 0;
case kAlpha_8_SkColorType: return 8;
case kRGB_565_SkColorType: return 5;
case kARGB_4444_SkColorType: return 4;
case kRGBA_8888_SkColorType: return 8;
case kRGB_888x_SkColorType: return 8;
case kBGRA_8888_SkColorType: return 8;
case kRGBA_1010102_SkColorType: return 10;
case kRGB_101010x_SkColorType: return 10;
case kGray_8_SkColorType: return 8; // counting gray as "rgb"
case kRGBA_F16Norm_SkColorType: return 10; // just counting the mantissa
case kRGBA_F16_SkColorType: return 10; // just counting the mantissa
case kRGBA_F32_SkColorType: return 23; // just counting the mantissa
}
SK_ABORT("Unexpected color type.");
return 0;
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(AsyncReadPixels, reporter, ctxInfo) {
struct Context {
SkPixmap* fPixmap = nullptr;
@ -708,9 +768,9 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(AsyncReadPixels, reporter, ctxInfo) {
for (auto origin : {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
static constexpr int kW = 16;
static constexpr int kH = 16;
for (int c = 0; c <= kLastEnum_SkColorType; ++c) {
auto ct = static_cast<SkColorType>(c);
auto info = SkImageInfo::Make(kW, kH, ct, kPremul_SkAlphaType, nullptr);
for (int sct = 0; sct <= kLastEnum_SkColorType; ++sct) {
auto surfCT = static_cast<SkColorType>(sct);
auto info = SkImageInfo::Make(kW, kH, surfCT, kPremul_SkAlphaType, nullptr);
auto surf = SkSurface::MakeRenderTarget(ctxInfo.grContext(), SkBudgeted::kNo, info, 1,
origin, nullptr);
if (!surf) {
@ -730,38 +790,61 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(AsyncReadPixels, reporter, ctxInfo) {
}
for (const auto& rect : {SkIRect::MakeWH(kW, kH), // full size
SkIRect::MakeLTRB(1, 2, kW - 3, kH - 4), // partial
SkIRect::MakeXYWH(1, 1, 0, 0), // zero size - fail
SkIRect::MakeWH(kW + 1, kH / 2)}) { // too big - fail
SkAutoPixmapStorage pixmap;
Context context;
context.fPixmap = &pixmap;
info = SkImageInfo::Make(rect.width(), rect.height(), ct, kPremul_SkAlphaType,
nullptr);
pixmap.alloc(info);
memset(pixmap.writable_addr(), 0xAB, pixmap.computeByteSize());
surf->asyncReadPixels(ct, kPremul_SkAlphaType, nullptr, rect, callback, &context);
while (!context.fCalled) {
ctxInfo.grContext()->checkAsyncWorkCompletion();
}
if (rect.isEmpty() || !SkIRect::MakeWH(kW, kH).contains(rect)) {
REPORTER_ASSERT(reporter, !context.fSuceeded);
}
if (context.fSuceeded) {
// We use a synchronous read as the source of truth.
SkAutoPixmapStorage ref;
ref.alloc(info);
memset(ref.writable_addr(), 0xCD, ref.computeByteSize());
if (!surf->readPixels(ref, rect.fLeft, rect.fTop)) {
continue;
}
const auto* a = static_cast<const char*>(pixmap.addr());
const auto* b = static_cast<const char*>(ref.addr());
for (int j = 0; j < pixmap.height();
++j, a += pixmap.rowBytes(), b += ref.rowBytes()) {
if (memcmp(a, b, pixmap.width() * SkColorTypeBytesPerPixel(ct))) {
ERRORF(reporter, "Failed. CT: %d, j: %d", ct, j);
break;
SkIRect::MakeXYWH(1, 1, 0, 0), // empty: fail
SkIRect::MakeWH(kW + 1, kH / 2)}) { // too wide: fail
for (int rct = 0; rct <= kLastEnum_SkColorType; ++rct) {
auto readCT = static_cast<SkColorType>(rct);
for (const sk_sp<SkColorSpace>& readCS :
{sk_sp<SkColorSpace>(), SkColorSpace::MakeSRGBLinear()}) {
SkAutoPixmapStorage result;
Context context;
context.fPixmap = &result;
info = SkImageInfo::Make(rect.width(), rect.height(), readCT,
kPremul_SkAlphaType, readCS);
result.alloc(info);
memset(result.writable_addr(), 0xAB, result.computeByteSize());
surf->asyncReadPixels(readCT, kPremul_SkAlphaType, readCS, rect, callback,
&context);
while (!context.fCalled) {
ctxInfo.grContext()->checkAsyncWorkCompletion();
}
if (rect.isEmpty() || !SkIRect::MakeWH(kW, kH).contains(rect)) {
REPORTER_ASSERT(reporter, !context.fSuceeded);
}
if (!context.fSuceeded) {
continue;
}
// We use a synchronous read as the source of truth.
SkAutoPixmapStorage ref;
ref.alloc(info);
memset(ref.writable_addr(), 0xCD, ref.computeByteSize());
if (!surf->readPixels(ref, rect.fLeft, rect.fTop)) {
continue;
}
// When there is no conversion, don't allow a difference.
float tol = 0.f;
if (readCS || readCT != surfCT) {
// When there is a conversion allow a diff of two values when no
// color space conversion and three otherwise. Except for alpha where
// we allow no difference. Allow intermediate truncation to an 8 bit per
// channel format.
int rgbBits = std::min({min_rgb_channel_bits(readCT),
min_rgb_channel_bits(surfCT), 8});
tol = (readCS ? 3.f : 2.f) / (1 << rgbBits);
}
const float tols[4] = {tol, tol, tol, 0};
auto error = std::function<ComparePixmapsErrorReporter>(
[&](int x, int y, const float diffs[4]) {
ERRORF(reporter,
"Surf Color Type: %d, Read CT: %d, Rect [%d, %d, %d, %d]"
", origin: %d, CS conversion: %d\n"
"Error at %d, %d. Diff in floats: (%f, %f, %f %f)",
surfCT, readCT, rect.fLeft, rect.fTop, rect.fRight,
rect.fBottom, origin, (bool)readCS, x, y, diffs[0],
diffs[1], diffs[2], diffs[3]);
});
compare_pixmaps(ref, result, tols, error);
}
}
}