diff options
author | Brian Salomon <bsalomon@google.com> | 2018-06-01 14:13:43 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-06-01 18:59:16 +0000 |
commit | d494f6e3d68269d1a60e864561aa66a7b125be1a (patch) | |
tree | 21541972d90da1252afd765d932020be1dd17a8e /src/gpu/GrContext.cpp | |
parent | ee56b834fcd5e1834772d8e60195a21811833267 (diff) |
Replace GrContextPriv::read/writeSurfacePixels with GrContextPriv::read/WritePixels2
Change-Id: I08be0f347d360e97bf9a6d1aeb25f9f584cc3d89
Reviewed-on: https://skia-review.googlesource.com/131442
Commit-Queue: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Auto-Submit: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Diffstat (limited to 'src/gpu/GrContext.cpp')
-rw-r--r-- | src/gpu/GrContext.cpp | 449 |
1 files changed, 11 insertions, 438 deletions
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index b020729f67..d670319c7d 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -375,22 +375,6 @@ void GrContextPriv::flush(GrSurfaceProxy* proxy) { fContext->fDrawingManager->flush(proxy); } -bool sw_convert_to_premul(GrColorType srcColorType, int width, int height, size_t inRowBytes, - const void* inPixels, size_t outRowBytes, void* outPixels) { - SkColorType colorType = GrColorTypeToSkColorType(srcColorType); - if (kUnknown_SkColorType == colorType || 4 != SkColorTypeBytesPerPixel(colorType)) { - return false; - } - - for (int y = 0; y < height; y++) { - SkOpts::RGBA_to_rgbA((uint32_t*) outPixels, inPixels, width); - outPixels = SkTAddOffset<void>(outPixels, outRowBytes); - inPixels = SkTAddOffset<const void>(inPixels, inRowBytes); - } - - return true; -} - // TODO: This will be removed when GrSurfaceContexts are aware of their color types. // (skbug.com/6718) static bool valid_premul_config(GrPixelConfig config) { @@ -450,427 +434,16 @@ static bool valid_pixel_conversion(GrColorType cpuColorType, GrPixelConfig gpuCo return true; } -static bool pm_upm_must_round_trip(GrColorType cpuColorType, const SkColorSpace* cpuColorSpace) { - return !cpuColorSpace && - (GrColorType::kRGBA_8888 == cpuColorType || GrColorType::kBGRA_8888 == cpuColorType); -} - -// TODO: This will be removed when GrSurfaceContexts are aware of their color types. -// (skbug.com/6718) -static bool pm_upm_must_round_trip(GrPixelConfig surfaceConfig, - const SkColorSpace* surfaceColorSpace) { - return !surfaceColorSpace && - (kRGBA_8888_GrPixelConfig == surfaceConfig || kBGRA_8888_GrPixelConfig == surfaceConfig); -} - -static GrSRGBConversion determine_write_pixels_srgb_conversion(GrColorType srcColorType, - const SkColorSpace* srcColorSpace, - GrSRGBEncoded dstSRGBEncoded, - const SkColorSpace* dstColorSpace, - const GrCaps& caps) { - // No support for sRGB-encoded alpha. - if (GrColorTypeIsAlphaOnly(srcColorType)) { - return GrSRGBConversion::kNone; - } - // No conversions without GPU support for sRGB. (Legacy mode) - if (!caps.srgbSupport()) { - return GrSRGBConversion::kNone; - } - // If the GrSurfaceContext has no color space then it is in legacy mode. - if (!dstColorSpace) { - return GrSRGBConversion::kNone; - } - - bool srcColorSpaceIsSRGB = srcColorSpace && srcColorSpace->gammaCloseToSRGB(); - bool dstColorSpaceIsSRGB = dstColorSpace->gammaCloseToSRGB(); - - // For now we are assuming that if color space of the dst does not have sRGB gamma then the - // texture format is not sRGB encoded and vice versa. Note that we already checked for "legacy" - // mode being forced on by caps above. This may change in the future. We will then have to - // perform shader based conversions. - SkASSERT(dstColorSpaceIsSRGB == (GrSRGBEncoded::kYes == dstSRGBEncoded)); - - if (srcColorSpaceIsSRGB == dstColorSpaceIsSRGB) { - return GrSRGBConversion::kNone; - } - return srcColorSpaceIsSRGB ? GrSRGBConversion::kSRGBToLinear : GrSRGBConversion::kLinearToSRGB; -} - -static GrSRGBConversion determine_read_pixels_srgb_conversion(GrSRGBEncoded srcSRGBEncoded, - const SkColorSpace* srcColorSpace, - GrColorType dstColorType, - const SkColorSpace* dstColorSpace, - const GrCaps& caps) { - // This is symmetrical with the write version. - switch (determine_write_pixels_srgb_conversion(dstColorType, dstColorSpace, srcSRGBEncoded, - srcColorSpace, caps)) { - case GrSRGBConversion::kNone: return GrSRGBConversion::kNone; - case GrSRGBConversion::kLinearToSRGB: return GrSRGBConversion::kSRGBToLinear; - case GrSRGBConversion::kSRGBToLinear: return GrSRGBConversion::kLinearToSRGB; - } - return GrSRGBConversion::kNone; -} - bool GrContextPriv::writeSurfacePixels(GrSurfaceContext* dst, int left, int top, int width, int height, GrColorType srcColorType, SkColorSpace* srcColorSpace, const void* buffer, size_t rowBytes, uint32_t pixelOpsFlags) { - bool useLegacyPath = false; -#ifdef SK_LEGACY_GPU_PIXEL_OPS - useLegacyPath = true; -#endif - // Newly added color types/configs are only supported by the new code path. - if (srcColorType == GrColorType::kRGB_888x || - dst->asSurfaceProxy()->config() == kRGB_888_GrPixelConfig) { - useLegacyPath = false; - } - - if (!useLegacyPath) { - return this->writeSurfacePixels2(dst, left, top, width, height, srcColorType, srcColorSpace, - buffer, rowBytes, pixelOpsFlags); - } - - // TODO: Color space conversion - - ASSERT_SINGLE_OWNER_PRIV - RETURN_FALSE_IF_ABANDONED_PRIV - SkASSERT(dst); - ASSERT_OWNED_PROXY_PRIV(dst->asSurfaceProxy()); - GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "writeSurfacePixels", fContext); - - if (!dst->asSurfaceProxy()->instantiate(this->resourceProvider())) { - return false; - } - - GrSurfaceProxy* dstProxy = dst->asSurfaceProxy(); - GrSurface* dstSurface = dstProxy->priv().peekSurface(); - - // The src is unpremul but the dst is premul -> premul the src before or as part of the write - const bool premul = SkToBool(kUnpremul_PixelOpsFlag & pixelOpsFlags); - - if (!valid_pixel_conversion(srcColorType, dstProxy->config(), premul)) { - return false; - } - // There is no way to store alpha values in the dst. - if (GrColorTypeHasAlpha(srcColorType) && GrPixelConfigIsOpaque(dstProxy->config())) { - return false; - } - // The source has no alpha value and the dst is only alpha - if (!GrColorTypeHasAlpha(srcColorType) && GrPixelConfigIsAlphaOnly(dstProxy->config())) { - return false; - } - - // 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(srcColorType, srcColorSpace) && - pm_upm_must_round_trip(dstProxy->config(), dst->colorSpaceInfo().colorSpace()); - - // 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. - if (!GrSurfacePriv::AdjustWritePixelParams(dstSurface->width(), dstSurface->height(), - GrColorTypeBytesPerPixel(srcColorType), &left, &top, - &width, &height, &buffer, &rowBytes)) { - return false; - } - - GrGpu::DrawPreference drawPreference = premulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference - : GrGpu::kNoDraw_DrawPreference; - GrGpu::WritePixelTempDrawInfo tempDrawInfo; - GrSRGBConversion srgbConversion = determine_write_pixels_srgb_conversion( - srcColorType, srcColorSpace, GrPixelConfigIsSRGBEncoded(dstProxy->config()), - dst->colorSpaceInfo().colorSpace(), *fContext->contextPriv().caps()); - if (!fContext->fGpu->getWritePixelsInfo(dstSurface, dstProxy->origin(), width, height, - srcColorType, srgbConversion, &drawPreference, - &tempDrawInfo)) { - return false; - } - - if (!(kDontFlush_PixelOpsFlag & pixelOpsFlags) && dstSurface->surfacePriv().hasPendingIO()) { - this->flush(nullptr); // MDB TODO: tighten this - } - - sk_sp<GrTextureProxy> tempProxy; - if (GrGpu::kNoDraw_DrawPreference != drawPreference) { - tempProxy = this->proxyProvider()->createProxy(tempDrawInfo.fTempSurfaceDesc, - kTopLeft_GrSurfaceOrigin, - SkBackingFit::kApprox, SkBudgeted::kYes); - if (!tempProxy && GrGpu::kRequireDraw_DrawPreference == drawPreference) { - return false; - } - } - - // temp buffer for doing sw premul conversion, if needed. - SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0); - // 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(srcColorType, width, height, rowBytes, buffer, tmpRowBytes, - tmpPixels.get())) { - return false; - } - rowBytes = tmpRowBytes; - buffer = tmpPixels.get(); - } - - if (tempProxy) { - auto fp = GrSimpleTextureEffect::Make(tempProxy, SkMatrix::I()); - if (premulOnGpu) { - fp = fContext->createUPMToPMEffect(std::move(fp), useConfigConversionEffect); - } - fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle); - if (!fp) { - return false; - } - - if (!tempProxy->instantiate(this->resourceProvider())) { - return false; - } - GrTexture* texture = tempProxy->priv().peekTexture(); - - if (tempProxy->priv().hasPendingIO()) { - this->flush(tempProxy.get()); - } - - if (!fContext->fGpu->writePixels(texture, tempProxy->origin(), 0, 0, width, height, - tempDrawInfo.fWriteColorType, buffer, rowBytes)) { - return false; - } - tempProxy = nullptr; - - SkMatrix matrix; - matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top)); - GrRenderTargetContext* renderTargetContext = dst->asRenderTargetContext(); - if (!renderTargetContext) { - return false; - } - GrPaint paint; - paint.addColorFragmentProcessor(std::move(fp)); - paint.setPorterDuffXPFactory(SkBlendMode::kSrc); - 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(dstSurface, dstProxy->origin(), left, top, width, height, - srcColorType, buffer, rowBytes); - } - return true; -} - -bool GrContextPriv::readSurfacePixels(GrSurfaceContext* src, int left, int top, int width, - int height, GrColorType dstColorType, - SkColorSpace* dstColorSpace, void* buffer, size_t rowBytes, - uint32_t flags) { - bool useLegacyPath = false; -#ifdef SK_LEGACY_GPU_PIXEL_OPS - useLegacyPath = true; -#endif - // Newly added color types/configs are only supported by the new code path. - if (dstColorType == GrColorType::kRGB_888x || - src->asSurfaceProxy()->config() == kRGB_888_GrPixelConfig) { - useLegacyPath = false; - } - - if (!useLegacyPath) { - return this->readSurfacePixels2(src, left, top, width, height, dstColorType, dstColorSpace, - buffer, rowBytes, flags); - } - - // TODO: Color space conversion - - ASSERT_SINGLE_OWNER_PRIV - RETURN_FALSE_IF_ABANDONED_PRIV - SkASSERT(src); - ASSERT_OWNED_PROXY_PRIV(src->asSurfaceProxy()); - GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "readSurfacePixels", fContext); - SkASSERT(!(flags & kDontFlush_PixelOpsFlag)); - if (flags & kDontFlush_PixelOpsFlag) { - return false; - } - - // MDB TODO: delay this instantiation until later in the method - if (!src->asSurfaceProxy()->instantiate(this->resourceProvider())) { - return false; - } - - GrSurfaceProxy* srcProxy = src->asSurfaceProxy(); - GrSurface* srcSurface = srcProxy->priv().peekSurface(); - - // The src is premul but the dst is unpremul -> unpremul the src after or as part of the read - bool unpremul = SkToBool(kUnpremul_PixelOpsFlag & flags); - - if (!valid_pixel_conversion(dstColorType, srcProxy->config(), unpremul)) { - return false; - } - // The source is alpha-only but the dst is not. TODO: Make non-alpha channels in the dst be 0? - if (GrPixelConfigIsAlphaOnly(srcProxy->config()) && !GrColorTypeIsAlphaOnly(dstColorType)) { - return false; - } - // The source has no alpha, the dst is alpha-only. TODO: set all values in dst to 1? - if (GrPixelConfigIsOpaque(srcProxy->config()) && GrColorTypeIsAlphaOnly(dstColorType)) { - return false; - } - // Not clear if we should unpremul in this case. - if (!GrPixelConfigIsOpaque(srcProxy->config()) && !GrColorTypeHasAlpha(dstColorType)) { - return false; - } - - // 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(srcProxy->config(), src->colorSpaceInfo().colorSpace()) && - pm_upm_must_round_trip(dstColorType, 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. - if (!GrSurfacePriv::AdjustReadPixelParams(srcSurface->width(), srcSurface->height(), - GrColorTypeBytesPerPixel(dstColorType), &left, &top, - &width, &height, &buffer, &rowBytes)) { - return false; - } - - GrGpu::DrawPreference drawPreference = unpremulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference - : GrGpu::kNoDraw_DrawPreference; - GrGpu::ReadPixelTempDrawInfo tempDrawInfo; - GrSRGBConversion srgbConversion = determine_read_pixels_srgb_conversion( - GrPixelConfigIsSRGBEncoded(srcProxy->config()), src->colorSpaceInfo().colorSpace(), - dstColorType, dstColorSpace, *fContext->contextPriv().caps()); - - if (!fContext->fGpu->getReadPixelsInfo(srcSurface, srcProxy->origin(), width, height, rowBytes, - dstColorType, srgbConversion, &drawPreference, - &tempDrawInfo)) { - return false; - } - - if (srcSurface->surfacePriv().hasPendingWrite()) { - this->flush(nullptr); // MDB TODO: tighten this - } - - sk_sp<GrSurfaceProxy> proxyToRead = src->asSurfaceProxyRef(); - bool didTempDraw = false; - if (GrGpu::kNoDraw_DrawPreference != drawPreference) { - if (SkBackingFit::kExact == tempDrawInfo.fTempSurfaceFit) { - // We only respect this when the entire src is being read. Otherwise we can trigger too - // many odd ball texture sizes and trash the cache. - if (width != srcSurface->width() || height != srcSurface->height()) { - tempDrawInfo.fTempSurfaceFit= SkBackingFit::kApprox; - } - } - // TODO: Need to decide the semantics of this function for color spaces. Do we support - // conversion to 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. For - // now we simply infer an sRGB color space if the config is sRGB in order to avoid an - // illegal combination. - sk_sp<SkColorSpace> colorSpace; - if (GrPixelConfigIsSRGB(tempDrawInfo.fTempSurfaceDesc.fConfig)) { - colorSpace = SkColorSpace::MakeSRGB(); - } - sk_sp<GrRenderTargetContext> tempRTC = - fContext->contextPriv().makeDeferredRenderTargetContext( - tempDrawInfo.fTempSurfaceFit, - tempDrawInfo.fTempSurfaceDesc.fWidth, - tempDrawInfo.fTempSurfaceDesc.fHeight, - tempDrawInfo.fTempSurfaceDesc.fConfig, - std::move(colorSpace), - tempDrawInfo.fTempSurfaceDesc.fSampleCnt, - GrMipMapped::kNo, - kTopLeft_GrSurfaceOrigin); - if (tempRTC) { - // Adding discard to appease vulkan validation warning about loading uninitialized data - // on draw - tempRTC->discard(); - SkMatrix textureMatrix = SkMatrix::MakeTrans(SkIntToScalar(left), SkIntToScalar(top)); - sk_sp<GrTextureProxy> proxy = src->asTextureProxyRef(); - auto fp = GrSimpleTextureEffect::Make(std::move(proxy), textureMatrix); - if (unpremulOnGpu) { - fp = fContext->createPMToUPMEffect(std::move(fp), useConfigConversionEffect); - // We no longer need to do this on CPU after the read back. - unpremul = false; - } - fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle); - if (!fp) { - return false; - } - - GrPaint paint; - paint.addColorFragmentProcessor(std::move(fp)); - paint.setPorterDuffXPFactory(SkBlendMode::kSrc); - 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; - } - } - - if (!proxyToRead) { - return false; - } - - if (GrGpu::kRequireDraw_DrawPreference == drawPreference && !didTempDraw) { - return false; - } - GrColorType colorTypeToRead = dstColorType; - if (didTempDraw) { - this->flushSurfaceWrites(proxyToRead.get()); - colorTypeToRead = tempDrawInfo.fReadColorType; - } - - if (!proxyToRead->instantiate(this->resourceProvider())) { - return false; - } - - GrSurface* surfaceToRead = proxyToRead->priv().peekSurface(); - - if (!fContext->fGpu->readPixels(surfaceToRead, proxyToRead->origin(), left, top, width, height, - colorTypeToRead, buffer, rowBytes)) { - return false; - } - - // Perform umpremul conversion if we weren't able to perform it as a draw. - if (unpremul) { - SkColorType colorType = GrColorTypeToSkColorType(dstColorType); - if (kUnknown_SkColorType == colorType || 4 != SkColorTypeBytesPerPixel(colorType)) { - return false; - } - - for (int y = 0; y < height; y++) { - SkUnpremultiplyRow<false>((uint32_t*) buffer, (const uint32_t*) buffer, width); - buffer = SkTAddOffset<void>(buffer, rowBytes); - } - } - return true; -} - -bool GrContextPriv::writeSurfacePixels2(GrSurfaceContext* dst, int left, int top, int width, - int height, GrColorType srcColorType, - SkColorSpace* srcColorSpace, const void* buffer, - size_t rowBytes, uint32_t pixelOpsFlags) { ASSERT_SINGLE_OWNER_PRIV RETURN_FALSE_IF_ABANDONED_PRIV SkASSERT(dst); SkASSERT(buffer); ASSERT_OWNED_PROXY_PRIV(dst->asSurfaceProxy()); - GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "writeSurfacePixels2", fContext); + GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "writeSurfacePixels", fContext); if (GrColorType::kUnknown == srcColorType) { return false; @@ -934,8 +507,8 @@ bool GrContextPriv::writeSurfacePixels2(GrSurfaceContext* dst, int left, int top // the intermediate surface which gets corrected by a swizzle effect when drawing to the // dst. auto tmpColorType = canvas2DFastPath ? GrColorType::kRGBA_8888 : srcColorType; - if (!this->writeSurfacePixels2(tempCtx.get(), 0, 0, width, height, tmpColorType, - srcColorSpace, buffer, rowBytes, flags)) { + if (!this->writeSurfacePixels(tempCtx.get(), 0, 0, width, height, tmpColorType, + srcColorSpace, buffer, rowBytes, flags)) { return false; } if (canvas2DFastPath) { @@ -1030,16 +603,16 @@ bool GrContextPriv::writeSurfacePixels2(GrSurfaceContext* dst, int left, int top rowBytes); } -bool GrContextPriv::readSurfacePixels2(GrSurfaceContext* src, int left, int top, int width, - int height, GrColorType dstColorType, - SkColorSpace* dstColorSpace, void* buffer, size_t rowBytes, - uint32_t pixelOpsFlags) { +bool GrContextPriv::readSurfacePixels(GrSurfaceContext* src, int left, int top, int width, + int height, GrColorType dstColorType, + SkColorSpace* dstColorSpace, void* buffer, size_t rowBytes, + uint32_t pixelOpsFlags) { ASSERT_SINGLE_OWNER_PRIV RETURN_FALSE_IF_ABANDONED_PRIV SkASSERT(src); SkASSERT(buffer); ASSERT_OWNED_PROXY_PRIV(src->asSurfaceProxy()); - GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "readSurfacePixels2", fContext); + GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "readSurfacePixels", fContext); SkASSERT(!(pixelOpsFlags & kDontFlush_PixelOpsFlag)); if (pixelOpsFlags & kDontFlush_PixelOpsFlag) { @@ -1070,7 +643,7 @@ bool GrContextPriv::readSurfacePixels2(GrSurfaceContext* src, int left, int top, // This is the getImageData equivalent to the canvas2D putImageData fast path. We probably don't // care so much about getImageData performance. However, in order to ensure putImageData/ // getImageData in "legacy" mode are round-trippable we use the GPU to do the complementary - // unpremul step to writeSurfacePixels2's premul step (which is determined empirically in + // unpremul step to writeSurfacePixels's premul step (which is determined empirically in // fContext->vaildaPMUPMConversionExists()). bool canvas2DFastPath = unpremul && @@ -1130,8 +703,8 @@ bool GrContextPriv::readSurfacePixels2(GrSurfaceContext* src, int left, int top, return false; } uint32_t flags = canvas2DFastPath ? 0 : pixelOpsFlags; - return this->readSurfacePixels2(tempCtx.get(), 0, 0, width, height, dstColorType, - dstColorSpace, buffer, rowBytes, flags); + return this->readSurfacePixels(tempCtx.get(), 0, 0, width, height, dstColorType, + dstColorSpace, buffer, rowBytes, flags); } bool convert = unpremul; |