Further refactor read/writeSurfacePixels

Detect the situation where we're going to want to do PM/UPM, and want to
use GrConfigConverionEffect, but be unable (due to the lack of a
round-trip pair). This lets us hoist the SW premul work (in
writeSurfacePixels), and avoid all the cascading failure logic in both
functions. (We never try to create the PM/UPM effects unless we know
that they're going to work).

Bug: skia:5853
Change-Id: I0077447cd4be93bba273f8d2826b1ec0f4915c6c
Reviewed-on: https://skia-review.googlesource.com/13592
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Brian Osman 2017-04-17 11:48:28 -04:00 committed by Skia Commit-Bot
parent de1cad99b3
commit 409e74fb2c
2 changed files with 139 additions and 168 deletions

View File

@ -346,18 +346,20 @@ private:
void initCommon(const GrContextOptions&);
/**
* These functions create premul <-> unpremul effects if it is possible to generate a pair
* of effects that make a readToUPM->writeToPM->readToUPM cycle invariant. Otherwise, they
* return NULL. They also can perform a swizzle as part of the draw.
* These functions create premul <-> unpremul effects. If the second argument is 'true', they
* use the specialized round-trip effects from GrConfigConversionEffect, otherwise they
* create effects that do naive multiply or divide.
*/
sk_sp<GrFragmentProcessor> createPMToUPMEffect(sk_sp<GrFragmentProcessor>, GrPixelConfig);
sk_sp<GrFragmentProcessor> createUPMToPMEffect(sk_sp<GrFragmentProcessor>, GrPixelConfig);
/** Called before either of the above two functions to determine the appropriate fragment
processors for conversions. */
void testPMConversionsIfNecessary(uint32_t flags);
/** Returns true if we've determined that createPMtoUPMEffect and createUPMToPMEffect will
succeed for the passed in config. Otherwise we fall back to SW conversion. */
bool validPMUPMConversionExists(GrPixelConfig) const;
sk_sp<GrFragmentProcessor> createPMToUPMEffect(sk_sp<GrFragmentProcessor>,
bool useConfigConversionEffect);
sk_sp<GrFragmentProcessor> createUPMToPMEffect(sk_sp<GrFragmentProcessor>,
bool useConfigConversionEffect);
/**
* Returns true if createPMtoUPMEffect and createUPMToPMEffect will succeed for non-sRGB 8888
* configs. In other words, did we find a pair of round-trip preserving conversion effects?
*/
bool validPMUPMConversionExists();
/**
* A callback similar to the above for use by the TextBlobCache

View File

@ -284,6 +284,11 @@ static bool valid_pixel_conversion(GrPixelConfig srcConfig, GrPixelConfig dstCon
return true;
}
static bool pm_upm_must_round_trip(GrPixelConfig config, SkColorSpace* colorSpace) {
return !colorSpace &&
(kRGBA_8888_GrPixelConfig == config || kBGRA_8888_GrPixelConfig == config);
}
bool GrContextPriv::writeSurfacePixels(GrSurfaceProxy* dstProxy, SkColorSpace* dstColorSpace,
int left, int top, int width, int height,
GrPixelConfig srcConfig, SkColorSpace* srcColorSpace,
@ -303,12 +308,21 @@ bool GrContextPriv::writeSurfacePixels(GrSurfaceProxy* dstProxy, SkColorSpace* d
}
// The src is unpremul but the dst is premul -> premul the src before or as part of the write
bool premul = SkToBool(kUnpremul_PixelOpsFlag & pixelOpsFlags);
const bool premul = SkToBool(kUnpremul_PixelOpsFlag & pixelOpsFlags);
if (!valid_pixel_conversion(srcConfig, surface->config(), premul)) {
return false;
}
fContext->testPMConversionsIfNecessary(pixelOpsFlags);
// We need to guarantee round-trip conversion if we are reading and writing 8888 non-sRGB data,
// without any color spaces attached, and the caller wants us to premul.
bool useConfigConversionEffect = premul &&
pm_upm_must_round_trip(srcConfig, srcColorSpace) &&
pm_upm_must_round_trip(surface->config(), dstColorSpace);
// Are we going to try to premul as part of a draw? For the non-legacy case, we always allow
// this. GrConfigConversionEffect fails on some GPUs, so only allow this if it works perfectly.
bool premulOnGpu = premul &&
(!useConfigConversionEffect || fContext->validPMUPMConversionExists());
// Trim the params here so that if we wind up making a temporary surface it can be as small as
// necessary and because GrGpu::getWritePixelsInfo requires it.
@ -318,13 +332,8 @@ bool GrContextPriv::writeSurfacePixels(GrSurfaceProxy* dstProxy, SkColorSpace* d
return false;
}
GrGpu::DrawPreference drawPreference = GrGpu::kNoDraw_DrawPreference;
// Don't prefer to draw for the conversion (and thereby access a texture from the cache) when
// we've already determined that there isn't a roundtrip preserving conversion processor pair.
if (premul && fContext->validPMUPMConversionExists(srcConfig)) {
drawPreference = GrGpu::kCallerPrefersDraw_DrawPreference;
}
GrGpu::DrawPreference drawPreference = premulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference
: GrGpu::kNoDraw_DrawPreference;
GrGpu::WritePixelTempDrawInfo tempDrawInfo;
if (!fContext->fGpu->getWritePixelsInfo(surface, width, height, srcConfig,
&drawPreference, &tempDrawInfo)) {
@ -348,87 +357,63 @@ bool GrContextPriv::writeSurfacePixels(GrSurfaceProxy* dstProxy, SkColorSpace* d
// temp buffer for doing sw premul conversion, if needed.
SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0);
if (tempProxy) {
sk_sp<GrFragmentProcessor> texFP = GrSimpleTextureEffect::Make(
fContext->resourceProvider(), tempProxy, nullptr, SkMatrix::I());
sk_sp<GrFragmentProcessor> fp;
if (premul) {
fp = fContext->createUPMToPMEffect(texFP, tempProxy->config());
if (fp) {
// We no longer need to do this on CPU before the upload.
premul = false;
} else if (GrGpu::kCallerPrefersDraw_DrawPreference == drawPreference) {
// We only wanted to do the draw to perform the premul so don't bother.
tempProxy.reset(nullptr);
}
}
if (tempProxy) {
if (!fp) {
fp = std::move(texFP);
}
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle);
SkASSERT(fp);
if (tempProxy->priv().hasPendingIO()) {
this->flush(tempProxy.get());
}
GrTexture* texture = tempProxy->instantiate(fContext->resourceProvider());
if (!texture) {
return false;
}
if (premul) {
size_t tmpRowBytes = 4 * width;
tmpPixels.reset(width * height);
if (!sw_convert_to_premul(srcConfig, width, height, rowBytes, buffer, tmpRowBytes,
tmpPixels.get())) {
return false;
}
rowBytes = tmpRowBytes;
buffer = tmpPixels.get();
premul = false;
}
if (!fContext->fGpu->writePixels(texture, 0, 0, width, height,
tempDrawInfo.fWriteConfig, buffer,
rowBytes)) {
return false;
}
SkMatrix matrix;
matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top));
// TODO: Need to decide the semantics of this function for color spaces. Do we support
// conversion from a passed-in color space? For now, specifying nullptr means that this
// path will do no conversion, so it will match the behavior of the non-draw path.
GrRenderTarget* renderTarget = surface->asRenderTarget();
SkASSERT(renderTarget);
sk_sp<GrRenderTargetContext> renderTargetContext(
this->makeWrappedRenderTargetContext(sk_ref_sp(renderTarget), nullptr));
if (!renderTargetContext) {
return false;
}
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
paint.setAllowSRGBInputs(true);
SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, rect,
nullptr);
if (kFlushWrites_PixelOp & pixelOpsFlags) {
this->flushSurfaceWrites(renderTargetContext->asRenderTargetProxy());
}
// We need to do sw premul if we were unable to create a RT for drawing, or if we can't do the
// premul on the GPU
if (premul && (!tempProxy || !premulOnGpu)) {
size_t tmpRowBytes = 4 * width;
tmpPixels.reset(width * height);
if (!sw_convert_to_premul(srcConfig, width, height, rowBytes, buffer, tmpRowBytes,
tmpPixels.get())) {
return false;
}
rowBytes = tmpRowBytes;
buffer = tmpPixels.get();
}
if (!tempProxy) {
if (premul) {
size_t tmpRowBytes = 4 * width;
tmpPixels.reset(width * height);
if (!sw_convert_to_premul(srcConfig, width, height, rowBytes, buffer, tmpRowBytes,
tmpPixels.get())) {
return false;
}
rowBytes = tmpRowBytes;
buffer = tmpPixels.get();
premul = false;
if (tempProxy) {
sk_sp<GrFragmentProcessor> fp = GrSimpleTextureEffect::Make(
fContext->resourceProvider(), tempProxy, nullptr, SkMatrix::I());
if (premulOnGpu) {
fp = fContext->createUPMToPMEffect(std::move(fp), useConfigConversionEffect);
}
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle);
SkASSERT(fp);
if (tempProxy->priv().hasPendingIO()) {
this->flush(tempProxy.get());
}
GrTexture* texture = tempProxy->instantiate(fContext->resourceProvider());
if (!texture) {
return false;
}
if (!fContext->fGpu->writePixels(texture, 0, 0, width, height, tempDrawInfo.fWriteConfig,
buffer, rowBytes)) {
return false;
}
SkMatrix matrix;
matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top));
// TODO: Need to decide the semantics of this function for color spaces. Do we support
// conversion from a passed-in color space? For now, specifying nullptr means that this
// path will do no conversion, so it will match the behavior of the non-draw path.
GrRenderTarget* renderTarget = surface->asRenderTarget();
SkASSERT(renderTarget);
sk_sp<GrRenderTargetContext> renderTargetContext(
this->makeWrappedRenderTargetContext(sk_ref_sp(renderTarget), nullptr));
if (!renderTargetContext) {
return false;
}
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
paint.setAllowSRGBInputs(true);
SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, rect,
nullptr);
if (kFlushWrites_PixelOp & pixelOpsFlags) {
this->flushSurfaceWrites(renderTargetContext->asRenderTargetProxy());
}
} else {
return fContext->fGpu->writePixels(surface, left, top, width, height, srcConfig,
buffer, rowBytes);
}
@ -459,7 +444,16 @@ bool GrContextPriv::readSurfacePixels(GrSurfaceProxy* srcProxy, SkColorSpace* sr
return false;
}
fContext->testPMConversionsIfNecessary(flags);
// We need to guarantee round-trip conversion if we are reading and writing 8888 non-sRGB data,
// without any color spaces attached, and the caller wants us to unpremul.
bool useConfigConversionEffect = unpremul &&
pm_upm_must_round_trip(src->config(), srcColorSpace) &&
pm_upm_must_round_trip(dstConfig, dstColorSpace);
// Are we going to try to unpremul as part of a draw? For the non-legacy case, we always allow
// this. GrConfigConversionEffect fails on some GPUs, so only allow this if it works perfectly.
bool unpremulOnGpu = unpremul &&
(!useConfigConversionEffect || fContext->validPMUPMConversionExists());
// Adjust the params so that if we wind up using an intermediate surface we've already done
// all the trimming and the temporary can be the min size required.
@ -469,13 +463,8 @@ bool GrContextPriv::readSurfacePixels(GrSurfaceProxy* srcProxy, SkColorSpace* sr
return false;
}
GrGpu::DrawPreference drawPreference = GrGpu::kNoDraw_DrawPreference;
// Don't prefer to draw for the conversion (and thereby access a texture from the cache) when
// we've already determined that there isn't a roundtrip preserving conversion processor pair.
if (unpremul && fContext->validPMUPMConversionExists(src->config())) {
drawPreference = GrGpu::kCallerPrefersDraw_DrawPreference;
}
GrGpu::DrawPreference drawPreference = unpremulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference
: GrGpu::kNoDraw_DrawPreference;
GrGpu::ReadPixelTempDrawInfo tempDrawInfo;
if (!fContext->fGpu->getReadPixelsInfo(src, width, height, rowBytes, dstConfig,
&drawPreference, &tempDrawInfo)) {
@ -510,38 +499,27 @@ bool GrContextPriv::readSurfacePixels(GrSurfaceProxy* srcProxy, SkColorSpace* sr
if (tempRTC) {
SkMatrix textureMatrix = SkMatrix::MakeTrans(SkIntToScalar(left), SkIntToScalar(top));
sk_sp<GrTextureProxy> proxy = sk_ref_sp(srcProxy->asTextureProxy());
sk_sp<GrFragmentProcessor> texFP = GrSimpleTextureEffect::Make(
sk_sp<GrFragmentProcessor> fp = GrSimpleTextureEffect::Make(
fContext->resourceProvider(), proxy, nullptr, textureMatrix);
sk_sp<GrFragmentProcessor> fp;
if (unpremul) {
fp = fContext->createPMToUPMEffect(texFP, proxy->config());
if (fp) {
// We no longer need to do this on CPU after the read back.
unpremul = false;
} else if (GrGpu::kCallerPrefersDraw_DrawPreference == drawPreference) {
// We only wanted to do the draw to perform the unpremul so don't bother.
tempRTC.reset(nullptr);
}
if (unpremulOnGpu) {
fp = fContext->createPMToUPMEffect(std::move(fp), useConfigConversionEffect);
// We no longer need to do this on CPU after the read back.
unpremul = false;
}
if (tempRTC) {
if (!fp) {
fp = std::move(texFP);
}
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle);
SkASSERT(fp);
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle);
SkASSERT(fp);
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
paint.setAllowSRGBInputs(true);
SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
tempRTC->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect,
nullptr);
proxyToRead = tempRTC->asTextureProxyRef();
left = 0;
top = 0;
didTempDraw = true;
}
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
paint.setAllowSRGBInputs(true);
SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
tempRTC->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect,
nullptr);
proxyToRead = tempRTC->asTextureProxyRef();
left = 0;
top = 0;
didTempDraw = true;
}
}
@ -914,30 +892,19 @@ void test_pm_conversions(GrContext* ctx, int* pmToUPMValue, int* upmToPMValue) {
}
}
void GrContext::testPMConversionsIfNecessary(uint32_t flags) {
ASSERT_SINGLE_OWNER
if (SkToBool(GrContextPriv::kUnpremul_PixelOpsFlag & flags)) {
if (!fDidTestPMConversions) {
test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
fDidTestPMConversions = true;
}
}
}
sk_sp<GrFragmentProcessor> GrContext::createPMToUPMEffect(sk_sp<GrFragmentProcessor> fp,
GrPixelConfig config) {
bool useConfigConversionEffect) {
ASSERT_SINGLE_OWNER
// We should have already called this->testPMConversionsIfNecessary().
SkASSERT(fDidTestPMConversions);
// We have specialized effects that guarantee round-trip conversion for these formats
if (kRGBA_8888_GrPixelConfig == config || kBGRA_8888_GrPixelConfig == config) {
// We have specialized effects that guarantee round-trip conversion for some formats
if (useConfigConversionEffect) {
// We should have already called this->validPMUPMConversionExists() in this case
SkASSERT(fDidTestPMConversions);
// ...and it should have succeeded
SkASSERT(this->validPMUPMConversionExists());
GrConfigConversionEffect::PMConversion pmToUPM =
static_cast<GrConfigConversionEffect::PMConversion>(fPMToUPMConversion);
if (GrConfigConversionEffect::kPMConversionCnt != pmToUPM) {
return GrConfigConversionEffect::Make(std::move(fp), pmToUPM);
} else {
return nullptr;
}
return GrConfigConversionEffect::Make(std::move(fp), pmToUPM);
} else {
// For everything else (sRGB, half-float, etc...), it doesn't make sense to try and
// explicitly round the results. Just do the obvious, naive thing in the shader.
@ -946,19 +913,20 @@ sk_sp<GrFragmentProcessor> GrContext::createPMToUPMEffect(sk_sp<GrFragmentProces
}
sk_sp<GrFragmentProcessor> GrContext::createUPMToPMEffect(sk_sp<GrFragmentProcessor> fp,
GrPixelConfig config) {
bool useConfigConversionEffect) {
ASSERT_SINGLE_OWNER
// We should have already called this->testPMConversionsIfNecessary().
SkASSERT(fDidTestPMConversions);
// We have specialized effects that guarantee round-trip conversion for these formats
if (kRGBA_8888_GrPixelConfig == config || kBGRA_8888_GrPixelConfig == config) {
if (useConfigConversionEffect) {
// We should have already called this->validPMUPMConversionExists() in this case
SkASSERT(fDidTestPMConversions);
// ...and it should have succeeded
SkASSERT(this->validPMUPMConversionExists());
GrConfigConversionEffect::PMConversion upmToPM =
static_cast<GrConfigConversionEffect::PMConversion>(fUPMToPMConversion);
if (GrConfigConversionEffect::kPMConversionCnt != upmToPM) {
return GrConfigConversionEffect::Make(std::move(fp), upmToPM);
} else {
return nullptr;
}
return GrConfigConversionEffect::Make(std::move(fp), upmToPM);
} else {
// For everything else (sRGB, half-float, etc...), it doesn't make sense to try and
// explicitly round the results. Just do the obvious, naive thing in the shader.
@ -966,14 +934,15 @@ sk_sp<GrFragmentProcessor> GrContext::createUPMToPMEffect(sk_sp<GrFragmentProces
}
}
bool GrContext::validPMUPMConversionExists(GrPixelConfig config) const {
bool GrContext::validPMUPMConversionExists() {
ASSERT_SINGLE_OWNER
// We should have already called this->testPMConversionsIfNecessary().
SkASSERT(fDidTestPMConversions);
if (!fDidTestPMConversions) {
test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
fDidTestPMConversions = true;
}
// The PM<->UPM tests fail or succeed together so we only need to check one.
// For F16, we always allow PM/UPM conversion on the GPU, even if it doesn't round-trip.
return GrConfigConversionEffect::kPMConversionCnt != fPMToUPMConversion ||
kRGBA_half_GrPixelConfig == config;
return GrConfigConversionEffect::kPMConversionCnt != fPMToUPMConversion;
}
//////////////////////////////////////////////////////////////////////////////