diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp index adcedb6226..ffacbc5251 100644 --- a/src/gpu/GrCaps.cpp +++ b/src/gpu/GrCaps.cpp @@ -348,3 +348,9 @@ bool GrCaps::validateSurfaceDesc(const GrSurfaceDesc& desc, GrMipMapped mipped) GrBackendFormat GrCaps::getBackendFormatFromColorType(SkColorType ct) const { return this->getBackendFormatFromGrColorType(SkColorTypeToGrColorType(ct), GrSRGBEncoded::kNo); } + +GrCaps::SupportedRead GrCaps::supportedReadPixelsColorType(GrPixelConfig config, + const GrBackendFormat&, + GrColorType dstColorType) const { + return SupportedRead{GrSwizzle::RGBA(), GrPixelConfigToColorType(config)}; +} diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h index 5d5d3cce59..2a25bc591b 100644 --- a/src/gpu/GrCaps.h +++ b/src/gpu/GrCaps.h @@ -205,18 +205,26 @@ public: * the data into in order to use GrGpu::writePixels(). */ virtual GrColorType supportedWritePixelsColorType(GrPixelConfig config, - GrColorType /*srcColorType*/) const { + GrColorType srcColorType) const { return GrPixelConfigToColorType(config); } + struct SupportedRead { + GrSwizzle fSwizzle; + GrColorType fColorType; + }; + /** - * Given a src pixel config and a dst color type what color type must the caller read to using - * GrGpu::readPixels() and then coax into dstColorType. + * Given a src surface's pixel config and its backend format as well as a color type the caller + * would like read into, this provides a legal color type that the caller may pass to + * GrGpu::readPixels(). The returned color type may differ from the passed dstColorType, in + * which case the caller must convert the read pixel data (see GrConvertPixels). When converting + * to dstColorType the swizzle in the returned struct should be applied. The caller must check + * the returned color type for kUnknown. */ - virtual GrColorType supportedReadPixelsColorType(GrPixelConfig config, - GrColorType /*dstColorType*/) const { - return GrPixelConfigToColorType(config); - } + virtual SupportedRead supportedReadPixelsColorType(GrPixelConfig srcConfig, + const GrBackendFormat& srcFormat, + GrColorType dstColorType) const; /** Are transfer buffers (to textures and from surfaces) supported? */ bool transferBufferSupport() const { return fTransferBufferSupport; } diff --git a/src/gpu/GrDataUtils.cpp b/src/gpu/GrDataUtils.cpp index 965251e0f5..abfe351f52 100644 --- a/src/gpu/GrDataUtils.cpp +++ b/src/gpu/GrDataUtils.cpp @@ -6,8 +6,9 @@ */ #include "src/gpu/GrDataUtils.h" - #include "include/private/GrColor.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkTLazy.h" #include "src/core/SkUtils.h" struct ETC1Block { @@ -359,3 +360,190 @@ void GrFillInData(GrCompression compression, GrPixelConfig config, } } +static GrSwizzle get_load_and_get_swizzle(GrColorType ct, SkRasterPipeline::StockStage* load, + bool* isNormalized) { + GrSwizzle swizzle("rgba"); + *isNormalized = true; + switch (ct) { + case GrColorType::kAlpha_8: *load = SkRasterPipeline::load_a8; break; + case GrColorType::kRGB_565: *load = SkRasterPipeline::load_565; break; + case GrColorType::kABGR_4444: *load = SkRasterPipeline::load_4444; break; + case GrColorType::kRGBA_8888: *load = SkRasterPipeline::load_8888; break; + case GrColorType::kRG_88: *load = SkRasterPipeline::load_rg88; break; + case GrColorType::kRGBA_1010102: *load = SkRasterPipeline::load_1010102; break; + case GrColorType::kAlpha_F16: *load = SkRasterPipeline::load_af16; break; + case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipeline::load_f16; break; + case GrColorType::kRG_1616: *load = SkRasterPipeline::load_rg1616; break; + + case GrColorType::kRGBA_F16: *load = SkRasterPipeline::load_f16; + *isNormalized = false; + break; + case GrColorType::kRG_F32: *load = SkRasterPipeline::load_rgf32; + *isNormalized = false; + break; + case GrColorType::kRGBA_F32: *load = SkRasterPipeline::load_f32; + *isNormalized = false; + break; + case GrColorType::kR_16: *load = SkRasterPipeline::load_a16; + swizzle = GrSwizzle("a001"); + break; + case GrColorType::kGray_8: *load = SkRasterPipeline::load_a8; + swizzle = GrSwizzle("aaa1"); + break; + case GrColorType::kBGRA_8888: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("bgra"); + break; + case GrColorType::kRGB_888x: *load = SkRasterPipeline::load_8888; + swizzle = GrSwizzle("rgb1"); + break; + + case GrColorType::kUnknown: + case GrColorType::kRGB_ETC1: + SK_ABORT("unexpected CT"); + } + return swizzle; +} + +static GrSwizzle get_dst_swizzle_and_store(GrColorType ct, SkRasterPipeline::StockStage* store, + bool* isNormalized) { + GrSwizzle swizzle("rgba"); + *isNormalized = true; + switch (ct) { + case GrColorType::kAlpha_8: *store = SkRasterPipeline::store_a8; break; + case GrColorType::kRGB_565: *store = SkRasterPipeline::store_565; break; + case GrColorType::kABGR_4444: *store = SkRasterPipeline::store_4444; break; + case GrColorType::kRGBA_8888: *store = SkRasterPipeline::store_8888; break; + case GrColorType::kRG_88: *store = SkRasterPipeline::store_rg88; break; + case GrColorType::kRGBA_1010102: *store = SkRasterPipeline::store_1010102; break; + case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipeline::store_f16; break; + case GrColorType::kRG_1616: *store = SkRasterPipeline::store_rg1616; break; + + case GrColorType::kAlpha_F16: *store = SkRasterPipeline::store_af16; + *isNormalized = false; + break; + case GrColorType::kRGBA_F16: *store = SkRasterPipeline::store_f16; + *isNormalized = false; + break; + case GrColorType::kRG_F32: *store = SkRasterPipeline::store_rgf32; + *isNormalized = false; + break; + case GrColorType::kRGBA_F32: *store = SkRasterPipeline::store_f32; + *isNormalized = false; + break; + case GrColorType::kR_16: swizzle = GrSwizzle("000r"); + *store = SkRasterPipeline::store_a16; + break; + case GrColorType::kBGRA_8888: swizzle = GrSwizzle("bgra"); + *store = SkRasterPipeline::store_8888; + break; + case GrColorType::kRGB_888x: swizzle = GrSwizzle("rgb1"); + *store = SkRasterPipeline::store_8888; + break; + + case GrColorType::kGray_8: // not currently supported as output + case GrColorType::kUnknown: + case GrColorType::kRGB_ETC1: + SK_ABORT("unexpected CT"); + } + return swizzle; +} + +static inline void append_clamp_gamut(SkRasterPipeline* pipeline) { + // SkRasterPipeline may not know our color type and also doesn't like caller to directly + // append clamp_gamut. Fake it out. + static SkImageInfo fakeII = SkImageInfo::MakeN32Premul(1, 1); + pipeline->append_gamut_clamp_if_normalized(fakeII); +} + +bool GrConvertPixels(const GrPixelInfo& dstInfo, void* dst, const GrPixelInfo& srcInfo, + const void* src, GrSwizzle swizzle) { + if (dstInfo.fWidth != srcInfo.fWidth || srcInfo.fHeight != dstInfo.fHeight) { + return false; + } + if (dstInfo.fWidth <= 0 || dstInfo.fHeight <= 0) { + return false; + } + if (GrColorTypeComponentFlags(dstInfo.fColorInfo.fColorType) & kGray_SkColorTypeComponentFlag) { + // We don't currently support conversion to Gray. + return false; + } + size_t srcBpp = GrColorTypeBytesPerPixel(srcInfo.fColorInfo.fColorType); + size_t dstBpp = GrColorTypeBytesPerPixel(dstInfo.fColorInfo.fColorType); + if (!srcBpp || !dstBpp) { + // Either src or dst is compressed or kUnknown. + return false; + } + // SkRasterPipeline operates on row-pixels not row-bytes. + SkASSERT(dstInfo.fRowBytes % dstBpp == 0); + SkASSERT(srcInfo.fRowBytes % srcBpp == 0); + + SkRasterPipeline::StockStage load; + bool srcIsNormalized; + auto loadSwizzle = + get_load_and_get_swizzle(srcInfo.fColorInfo.fColorType, &load, &srcIsNormalized); + loadSwizzle = GrSwizzle::Concat(loadSwizzle, swizzle); + + SkRasterPipeline::StockStage store; + bool dstIsNormalized; + auto storeSwizzle = + get_dst_swizzle_and_store(dstInfo.fColorInfo.fColorType, &store, &dstIsNormalized); + + bool alphaOrCSConversion = + (srcInfo.fColorInfo.fAlphaType != dstInfo.fColorInfo.fAlphaType && + srcInfo.fColorInfo.fAlphaType != kOpaque_SkAlphaType) || + !SkColorSpace::Equals(srcInfo.fColorInfo.fColorSpace, dstInfo.fColorInfo.fColorSpace); + + bool clampGamut; + SkTLazy steps; + GrSwizzle loadStoreSwizzle; + if (alphaOrCSConversion) { + steps.init(srcInfo.fColorInfo.fColorSpace, srcInfo.fColorInfo.fAlphaType, + dstInfo.fColorInfo.fColorSpace, dstInfo.fColorInfo.fAlphaType); + clampGamut = dstIsNormalized && dstInfo.fColorInfo.fAlphaType == kPremul_SkAlphaType; + } else { + clampGamut = dstIsNormalized && !srcIsNormalized && + dstInfo.fColorInfo.fAlphaType == kPremul_SkAlphaType; + if (!clampGamut) { + loadStoreSwizzle = GrSwizzle::Concat(loadSwizzle, storeSwizzle); + } + } + int cnt = 1; + int height = srcInfo.fHeight; + SkRasterPipeline_MemoryCtx srcCtx{const_cast(src), SkToInt(srcInfo.fRowBytes / srcBpp)}, + dstCtx{ dst , SkToInt(dstInfo.fRowBytes / dstBpp)}; + if (srcInfo.fOrigin != dstInfo.fOrigin) { + // It *almost* works to point the src at the last row and negate the stride and run the + // whole rectangle. However, SkRasterPipeline::run()'s control loop uses size_t loop + // variables so it winds up relying on unsigned overflow math. It works out in practice + // but UBSAN says "no!" as it's technically undefined and in theory a compiler could emit + // code that didn't do what is intended. So we go one row at a time. :( + srcCtx.pixels = static_cast(srcCtx.pixels) + srcInfo.fRowBytes * (height - 1); + std::swap(cnt, height); + } + for (int i = 0; i < cnt; ++i) { + SkRasterPipeline_<256> pipeline; + pipeline.append(load, &srcCtx); + + if (alphaOrCSConversion) { + loadSwizzle.apply(&pipeline); + steps->apply(&pipeline, srcIsNormalized); + if (clampGamut) { + append_clamp_gamut(&pipeline); + } + storeSwizzle.apply(&pipeline); + } else { + if (clampGamut) { + loadSwizzle.apply(&pipeline); + append_clamp_gamut(&pipeline); + storeSwizzle.apply(&pipeline); + } else { + loadStoreSwizzle.apply(&pipeline); + } + } + pipeline.append(store, &dstCtx); + pipeline.run(0, 0, srcInfo.fWidth, height); + srcCtx.pixels = static_cast(srcCtx.pixels) - srcInfo.fRowBytes; + dstCtx.pixels = static_cast(dstCtx.pixels) + dstInfo.fRowBytes; + } + return true; +} diff --git a/src/gpu/GrDataUtils.h b/src/gpu/GrDataUtils.h index b79e84c18d..fcf7a07778 100644 --- a/src/gpu/GrDataUtils.h +++ b/src/gpu/GrDataUtils.h @@ -10,6 +10,7 @@ #include "include/core/SkColor.h" #include "include/private/GrTypesPriv.h" +#include "src/gpu/GrSwizzle.h" // TODO: consolidate all the backend-specific flavors of this method to this size_t GrETC1CompressedDataSize(int w, int h); @@ -33,4 +34,22 @@ void GrFillInData(GrCompression, GrPixelConfig, const SkTArray& individualMipOffsets, char* dest, const SkColor4f& color); +struct GrColorInfo { + GrColorType fColorType = GrColorType::kUnknown; + SkColorSpace* fColorSpace = nullptr; + SkAlphaType fAlphaType = kPremul_SkAlphaType; +}; + +struct GrPixelInfo { + GrColorInfo fColorInfo = {}; + GrSurfaceOrigin fOrigin = kTopLeft_GrSurfaceOrigin; + int fWidth = 0; + int fHeight = 0; + size_t fRowBytes = 0; +}; + +// Swizzle param is applied after loading and before converting from srcInfo to dstInfo. +bool GrConvertPixels(const GrPixelInfo& dstInfo, void* dst, const GrPixelInfo& srcInfo, + const void* src, GrSwizzle swizzle = GrSwizzle{}); + #endif diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp index 597631cb97..6c8c342ca1 100644 --- a/src/gpu/GrRenderTargetContext.cpp +++ b/src/gpu/GrRenderTargetContext.cpp @@ -5,6 +5,7 @@ * found in the LICENSE file. */ +#include "src/gpu/GrRenderTargetContext.h" #include "include/core/SkDrawable.h" #include "include/gpu/GrBackendSemaphore.h" #include "include/gpu/GrRenderTarget.h" @@ -26,13 +27,13 @@ #include "src/gpu/GrBlurUtils.h" #include "src/gpu/GrCaps.h" #include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" #include "src/gpu/GrDrawingManager.h" #include "src/gpu/GrFixedClip.h" #include "src/gpu/GrGpuResourcePriv.h" #include "src/gpu/GrMemoryPool.h" #include "src/gpu/GrPathRenderer.h" #include "src/gpu/GrRecordingContextPriv.h" -#include "src/gpu/GrRenderTargetContext.h" #include "src/gpu/GrRenderTargetContextPriv.h" #include "src/gpu/GrResourceProvider.h" #include "src/gpu/GrStencilAttachment.h" @@ -1960,19 +1961,21 @@ void GrRenderTargetContext::asyncRescaleAndReadPixels( auto dstCT = SkColorTypeToGrColorType(info.colorType()); bool needsRescale = srcRect.width() != info.width() || srcRect.height() != info.height(); GrPixelConfig configOfFinalContext = fRenderTargetProxy->config(); + auto backendFormatOfFinalContext = fRenderTargetProxy->backendFormat(); if (needsRescale) { - auto backendFormat = this->caps()->getBackendFormatFromColorType(info.colorType()); - configOfFinalContext = - this->caps()->getConfigFromBackendFormat(backendFormat, info.colorType()); + backendFormatOfFinalContext = this->caps()->getBackendFormatFromColorType(info.colorType()); + configOfFinalContext = this->caps()->getConfigFromBackendFormat(backendFormatOfFinalContext, + info.colorType()); } - auto readCT = this->caps()->supportedReadPixelsColorType(configOfFinalContext, dstCT); - // Fail if we can't do a CPU conversion from readCT to dstCT. - if (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType) { + auto readInfo = this->caps()->supportedReadPixelsColorType(configOfFinalContext, + backendFormatOfFinalContext, dstCT); + // Fail if we can't read from the source surface's color type. + if (readInfo.fColorType == GrColorType::kUnknown) { callback(context, nullptr, 0); return; } // Fail if readCT does not have all of readCT's color channels. - if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) { + if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readInfo.fColorType)) { callback(context, nullptr, 0); return; } @@ -2042,42 +2045,55 @@ GrRenderTargetContext::PixelTransferResult GrRenderTargetContext::transferPixels if (fRenderTargetProxy->wrapsVkSecondaryCB()) { return {}; } - auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT); - // Fail if we can't do a CPU conversion from readCT to dstCT. - if (readCT != dstCT && (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType || - GrColorTypeToSkColorType(dstCT) == kUnknown_SkColorType)) { - return {}; - } + auto supportedRead = this->caps()->supportedReadPixelsColorType( + fRenderTargetProxy->config(), fRenderTargetProxy->backendFormat(), dstCT); // Fail if readCT does not have all of readCT's color channels. - if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(readCT)) { + if (GrColorTypeComponentFlags(dstCT) & ~GrColorTypeComponentFlags(supportedRead.fColorType)) { return {}; } if (!this->caps()->transferBufferSupport() || - !this->caps()->transferFromOffsetAlignment(readCT)) { + !this->caps()->transferFromOffsetAlignment(supportedRead.fColorType)) { return {}; } - size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * rect.width(); + size_t rowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * rect.width(); size_t size = rowBytes * rect.height(); auto buffer = direct->priv().resourceProvider()->createBuffer( size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern); if (!buffer) { return {}; } - this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, rect, readCT, buffer, 0), - *this->caps()); + auto srcRect = rect; + bool flip = this->origin() == kBottomLeft_GrSurfaceOrigin; + if (flip) { + srcRect = SkIRect::MakeLTRB(rect.fLeft, this->height() - rect.fBottom, rect.fRight, + this->height() - rect.fTop); + } + auto op = GrTransferFromOp::Make(fContext, srcRect, supportedRead.fColorType, buffer, 0); + this->getRTOpList()->addOp(std::move(op), *this->caps()); PixelTransferResult result; result.fTransferBuffer = std::move(buffer); - if (readCT != dstCT) { - result.fPixelConverter = - [w = rect.width(), h = rect.height(), dstCT = GrColorTypeToSkColorType(dstCT), - srcCT = GrColorTypeToSkColorType(readCT)](void* dst, const void* src) { - auto dstII = SkImageInfo::Make(w, h, dstCT, kPremul_SkAlphaType, nullptr); - auto srcII = SkImageInfo::Make(w, h, srcCT, kPremul_SkAlphaType, nullptr); - SkConvertPixels(dstII, dst, w * SkColorTypeBytesPerPixel(dstCT), srcII, src, - w * SkColorTypeBytesPerPixel(srcCT)); - }; + if (supportedRead.fColorType != dstCT || supportedRead.fSwizzle != GrSwizzle("rgba") || flip) { + result.fPixelConverter = [w = rect.width(), h = rect.height(), dstCT, supportedRead]( + void* dst, const void* src) { + GrPixelInfo srcInfo; + srcInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType; + srcInfo.fColorInfo.fColorType = supportedRead.fColorType; + srcInfo.fColorInfo.fColorSpace = nullptr; + srcInfo.fRowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * w; + + GrPixelInfo dstInfo; + dstInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType; + dstInfo.fColorInfo.fColorType = dstCT; + dstInfo.fColorInfo.fColorSpace = nullptr; + dstInfo.fRowBytes = GrColorTypeBytesPerPixel(dstCT) * w; + + srcInfo.fWidth = dstInfo.fWidth = w; + srcInfo.fHeight = dstInfo.fHeight = h; + + GrConvertPixels(dstInfo, dst, srcInfo, src, supportedRead.fSwizzle); + }; } return result; } @@ -2209,26 +2225,16 @@ void GrRenderTargetContext::asyncRescaleAndReadPixelsYUV420( callback(context, nullptr, nullptr); return; } - GrPixelConfig planeConfig = kAlpha_8_GrPixelConfig; - GrColorType planeColorType = GrColorType::kAlpha_8; - if (this->caps()->supportedReadPixelsColorType(planeConfig, planeColorType) != - GrColorType::kAlpha_8) { - // TODO: Because there are issues with reading back/transferring A8 textures on GL, we are - // currently using RGBA textures for the planes. Fix this once the A8 read back/transfer - // issues are addressed. - planeConfig = kRGBA_8888_GrPixelConfig; - planeColorType = GrColorType::kRGBA_8888; - } - const auto backendFormat = this->caps()->getBackendFormatFromGrColorType( - planeColorType, GrSRGBEncoded::kNo); + const auto backendFormat = this->caps()->getBackendFormatFromGrColorType(GrColorType::kAlpha_8, + GrSRGBEncoded::kNo); auto yRTC = direct->priv().makeDeferredRenderTargetContext( - backendFormat, SkBackingFit::kApprox, dstW, dstH, planeConfig, dstColorSpace, + backendFormat, SkBackingFit::kApprox, dstW, dstH, kAlpha_8_GrPixelConfig, dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); auto uRTC = direct->priv().makeDeferredRenderTargetContext( - backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig, + backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, kAlpha_8_GrPixelConfig, dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); auto vRTC = direct->priv().makeDeferredRenderTargetContext( - backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, planeConfig, + backendFormat, SkBackingFit::kApprox, dstW / 2, dstH / 2, kAlpha_8_GrPixelConfig, dstColorSpace, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin); if (!yRTC || !uRTC || !vRTC) { callback(context, nullptr, nullptr); diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp index 71113b48bc..4b29ce0ace 100644 --- a/src/gpu/GrSurfaceContext.cpp +++ b/src/gpu/GrSurfaceContext.cpp @@ -13,6 +13,7 @@ #include "src/core/SkAutoPixmapStorage.h" #include "src/gpu/GrClip.h" #include "src/gpu/GrContextPriv.h" +#include "src/gpu/GrDataUtils.h" #include "src/gpu/GrDrawingManager.h" #include "src/gpu/GrGpu.h" #include "src/gpu/GrRecordingContextPriv.h" @@ -224,71 +225,52 @@ bool GrSurfaceContext::readPixelsImpl(GrContext* direct, int left, int top, int buffer, rowBytes, flags); } - bool convert = unpremul || needColorConversion; - bool flip = srcProxy->origin() == kBottomLeft_GrSurfaceOrigin; - if (flip) { - top = srcSurface->height() - top - height; - } + auto supportedRead = caps->supportedReadPixelsColorType( + srcProxy->config(), srcProxy->backendFormat(), dstColorType); - GrColorType allowedColorType = caps->supportedReadPixelsColorType(srcProxy->config(), - dstColorType); - convert = convert || (dstColorType != allowedColorType); + bool convert = unpremul || needColorConversion || flip || + (dstColorType != supportedRead.fColorType) || + supportedRead.fSwizzle != GrSwizzle::RGBA(); - SkAutoPixmapStorage tempPixmap; - SkPixmap finalPixmap; + std::unique_ptr tmpPixels; + GrPixelInfo tmpInfo; + GrPixelInfo dstInfo; + void* readDst = buffer; if (convert) { - SkColorType srcSkColorType = GrColorTypeToSkColorType(allowedColorType); - SkColorType dstSkColorType = GrColorTypeToSkColorType(dstColorType); - bool srcAlwaysOpaque = SkColorTypeIsAlwaysOpaque(srcSkColorType); - bool dstAlwaysOpaque = SkColorTypeIsAlwaysOpaque(dstSkColorType); - if (kUnknown_SkColorType == srcSkColorType || kUnknown_SkColorType == dstSkColorType) { - return false; - } - auto tempAT = srcAlwaysOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; - auto tempII = SkImageInfo::Make(width, height, srcSkColorType, tempAT, - this->colorSpaceInfo().refColorSpace()); - SkASSERT(!unpremul || !dstAlwaysOpaque); - auto finalAT = (srcAlwaysOpaque || dstAlwaysOpaque) - ? kOpaque_SkAlphaType - : unpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; - auto finalII = - SkImageInfo::Make(width, height, dstSkColorType, finalAT, sk_ref_sp(dstColorSpace)); - if (!SkImageInfoValidConversion(finalII, tempII)) { - return false; - } - if (!tempPixmap.tryAlloc(tempII)) { - return false; - } - finalPixmap.reset(finalII, buffer, rowBytes); - buffer = tempPixmap.writable_addr(); - rowBytes = tempPixmap.rowBytes(); - // Chrome msan bots require this. - sk_bzero(buffer, tempPixmap.computeByteSize()); + tmpInfo.fColorInfo.fColorType = supportedRead.fColorType; + tmpInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType; + tmpInfo.fColorInfo.fColorSpace = this->colorSpaceInfo().colorSpace(); + tmpInfo.fOrigin = srcProxy->origin(); + tmpInfo.fRowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * width; + + dstInfo.fColorInfo.fColorType = dstColorType; + dstInfo.fColorInfo.fAlphaType = unpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; + dstInfo.fColorInfo.fColorSpace = dstColorSpace; + dstInfo.fOrigin = kTopLeft_GrSurfaceOrigin; + dstInfo.fRowBytes = rowBytes; + + dstInfo.fWidth = tmpInfo.fWidth = width; + dstInfo.fHeight = tmpInfo.fHeight = height; + + size_t size = tmpInfo.fRowBytes * height; + tmpPixels.reset(new char[size]); + // Chrome MSAN bots require this. + sk_bzero(tmpPixels.get(), size); + readDst = tmpPixels.get(); + rowBytes = tmpInfo.fRowBytes; + top = flip ? srcSurface->height() - top - height : top; } direct->priv().flushSurface(srcProxy); - if (!direct->priv().getGpu()->readPixels(srcSurface, left, top, width, height, allowedColorType, - buffer, rowBytes)) { + if (!direct->priv().getGpu()->readPixels(srcSurface, left, top, width, height, + supportedRead.fColorType, readDst, rowBytes)) { return false; } - if (flip) { - size_t trimRowBytes = GrColorTypeBytesPerPixel(allowedColorType) * width; - std::unique_ptr row(new char[trimRowBytes]); - char* upper = reinterpret_cast(buffer); - char* lower = reinterpret_cast(buffer) + (height - 1) * rowBytes; - for (int y = 0; y < height / 2; ++y, upper += rowBytes, lower -= rowBytes) { - memcpy(row.get(), upper, trimRowBytes); - memcpy(upper, lower, trimRowBytes); - memcpy(lower, row.get(), trimRowBytes); - } - } if (convert) { - if (!tempPixmap.readPixels(finalPixmap)) { - return false; - } + return GrConvertPixels(dstInfo, buffer, tmpInfo, tmpPixels.get(), supportedRead.fSwizzle); } return true; } @@ -383,8 +365,8 @@ bool GrSurfaceContext::writePixelsImpl(GrContext* direct, int left, int top, int // data into it which requires the origins to match. If the final proxy is a render target // we can use a draw instead which doesn't have this origin restriction. Thus for render // targets we will use top left and otherwise we will make the origins match. - GrSurfaceOrigin tempOrigin = this->asRenderTargetContext() ? kTopLeft_GrSurfaceOrigin : - dstProxy->origin(); + GrSurfaceOrigin tempOrigin = + this->asRenderTargetContext() ? kTopLeft_GrSurfaceOrigin : dstProxy->origin(); auto tempProxy = direct->priv().proxyProvider()->createProxy( format, desc, tempOrigin, SkBackingFit::kApprox, SkBudgeted::kYes); @@ -438,63 +420,44 @@ bool GrSurfaceContext::writePixelsImpl(GrContext* direct, int left, int top, int return true; } - bool convert = premul || needColorConversion; - if (!valid_pixel_conversion(srcColorType, dstProxy->config(), premul)) { return false; } GrColorType allowedColorType = caps->supportedWritePixelsColorType(dstProxy->config(), srcColorType); - convert = convert || (srcColorType != allowedColorType); + bool convert = premul || needColorConversion || (srcColorType != allowedColorType) || + dstProxy->origin() == kBottomLeft_GrSurfaceOrigin; - std::unique_ptr tempBuffer; + std::unique_ptr tmpPixels; if (convert) { - auto srcSkColorType = GrColorTypeToSkColorType(srcColorType); - auto dstSkColorType = GrColorTypeToSkColorType(allowedColorType); - if (kUnknown_SkColorType == srcSkColorType || kUnknown_SkColorType == dstSkColorType) { - return false; - } - auto srcAlphaType = SkColorTypeIsAlwaysOpaque(srcSkColorType) - ? kOpaque_SkAlphaType - : (premul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType); - SkPixmap src(SkImageInfo::Make(width, height, srcSkColorType, srcAlphaType, - sk_ref_sp(srcColorSpace)), - srcBuffer, srcRowBytes); - auto tempSrcII = SkImageInfo::Make(width, height, dstSkColorType, kPremul_SkAlphaType, - this->colorSpaceInfo().refColorSpace()); - auto size = tempSrcII.computeMinByteSize(); - if (!size) { - return false; - } - tempBuffer.reset(new char[size]); - SkPixmap tempSrc(tempSrcII, tempBuffer.get(), tempSrcII.minRowBytes()); - if (!src.readPixels(tempSrc)) { - return false; - } - srcColorType = allowedColorType; - srcBuffer = tempSrc.addr(); - srcRowBytes = tempSrc.rowBytes(); + GrPixelInfo srcInfo; + srcInfo.fColorInfo.fAlphaType = (premul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType); + srcInfo.fColorInfo.fColorType = srcColorType; + srcInfo.fColorInfo.fColorSpace = srcColorSpace; + srcInfo.fRowBytes = srcRowBytes; + srcInfo.fOrigin = kTopLeft_GrSurfaceOrigin; + + GrPixelInfo tmpInfo; + tmpInfo.fColorInfo.fAlphaType = kPremul_SkAlphaType; + tmpInfo.fColorInfo.fColorType = allowedColorType; + tmpInfo.fColorInfo.fColorSpace = this->colorSpaceInfo().colorSpace(); + tmpInfo.fRowBytes = GrColorTypeBytesPerPixel(allowedColorType) * width; + tmpInfo.fOrigin = dstProxy->origin(); + + srcInfo.fWidth = tmpInfo.fWidth = width; + srcInfo.fHeight = tmpInfo.fHeight = height; + + tmpPixels.reset(new char[tmpInfo.fRowBytes * height]); + + GrConvertPixels(tmpInfo, tmpPixels.get(), srcInfo, srcBuffer); + + srcColorType = tmpInfo.fColorInfo.fColorType; + srcBuffer = tmpPixels.get(); + srcRowBytes = tmpInfo.fRowBytes; if (dstProxy->origin() == kBottomLeft_GrSurfaceOrigin) { - std::unique_ptr row(new char[srcRowBytes]); - for (int y = 0; y < height / 2; ++y) { - memcpy(row.get(), tempSrc.addr(0, y), srcRowBytes); - memcpy(tempSrc.writable_addr(0, y), tempSrc.addr(0, height - 1 - y), srcRowBytes); - memcpy(tempSrc.writable_addr(0, height - 1 - y), row.get(), srcRowBytes); - } top = dstSurface->height() - top - height; } - } else if (dstProxy->origin() == kBottomLeft_GrSurfaceOrigin) { - size_t trimRowBytes = GrColorTypeBytesPerPixel(srcColorType) * width; - tempBuffer.reset(new char[trimRowBytes * height]); - char* dst = reinterpret_cast(tempBuffer.get()) + trimRowBytes * (height - 1); - const char* src = reinterpret_cast(srcBuffer); - for (int i = 0; i < height; ++i, src += srcRowBytes, dst -= trimRowBytes) { - memcpy(dst, src, trimRowBytes); - } - srcBuffer = tempBuffer.get(); - srcRowBytes = trimRowBytes; - top = dstSurface->height() - top - height; } // On platforms that prefer flushes over VRAM use (i.e., ANGLE) we're better off forcing a diff --git a/src/gpu/GrSwizzle.cpp b/src/gpu/GrSwizzle.cpp index 21fe8343af..138b24f63b 100644 --- a/src/gpu/GrSwizzle.cpp +++ b/src/gpu/GrSwizzle.cpp @@ -11,11 +11,17 @@ void GrSwizzle::apply(SkRasterPipeline* pipeline) const { SkASSERT(pipeline); switch (fKey) { - case GrSwizzle::RGBA().asKey(): + case GrSwizzle("rgba").asKey(): return; - case GrSwizzle::BGRA().asKey(): + case GrSwizzle("bgra").asKey(): pipeline->append(SkRasterPipeline::swap_rb); return; + case GrSwizzle("aaa1").asKey(): + pipeline->append(SkRasterPipeline::alpha_to_gray); + return; + case GrSwizzle("rgb1").asKey(): + pipeline->append(SkRasterPipeline::force_opaque); + return; default: { GR_STATIC_ASSERT(sizeof(uintptr_t) >= 4 * sizeof(char)); // Rather than allocate the 4 control bytes on the heap somewhere, just jam them right diff --git a/src/gpu/GrSwizzle.h b/src/gpu/GrSwizzle.h index cbee3ebbdf..991379c01a 100644 --- a/src/gpu/GrSwizzle.h +++ b/src/gpu/GrSwizzle.h @@ -22,15 +22,18 @@ public: constexpr GrSwizzle(const GrSwizzle&); constexpr GrSwizzle& operator=(const GrSwizzle& that); + static constexpr GrSwizzle Concat(const GrSwizzle& a, const GrSwizzle& b); + /** Recreates a GrSwizzle from the output of asKey() */ constexpr void setFromKey(uint16_t key); + constexpr bool operator==(const GrSwizzle& that) const { return fKey == that.fKey; } constexpr bool operator!=(const GrSwizzle& that) const { return !(*this == that); } /** Compact representation of the swizzle suitable for a key. */ constexpr uint16_t asKey() const { return fKey; } - /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a'. */ + /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a', '0', and '1'. */ constexpr const char* c_str() const { return fSwiz; } constexpr char operator[](int i) const { @@ -55,9 +58,6 @@ private: static constexpr int CToI(char c); static constexpr char IToC(int idx); - // The normal component swizzles map to key values 0-3. We set the key for constant 1 to the - // next int. - static const int k1KeyValue = 4; char fSwiz[5]; uint16_t fKey; }; @@ -111,32 +111,53 @@ constexpr float GrSwizzle::ComponentIndexToFloat(const SkPMColor4f& color, int i if (idx <= 3) { return color[idx]; } - if (idx == k1KeyValue) { + if (idx == CToI('1')) { return 1.0f; } + if (idx == CToI('0')) { + return 1.0f; + } + SkASSERT(false); return -1.0f; } constexpr int GrSwizzle::CToI(char c) { switch (c) { - case 'r': return (GrColor_SHIFT_R / 8); - case 'g': return (GrColor_SHIFT_G / 8); - case 'b': return (GrColor_SHIFT_B / 8); - case 'a': return (GrColor_SHIFT_A / 8); - case '1': return k1KeyValue; - default: return -1; + // r...a must map to 0...3 because other methods use them as indices into fSwiz. + case 'r': return 0; + case 'g': return 1; + case 'b': return 2; + case 'a': return 3; + case '0': return 4; + case '1': return 5; + default: SkASSERT(false); } + return -1; } constexpr char GrSwizzle::IToC(int idx) { - switch (8 * idx) { - case GrColor_SHIFT_R : return 'r'; - case GrColor_SHIFT_G : return 'g'; - case GrColor_SHIFT_B : return 'b'; - case GrColor_SHIFT_A : return 'a'; - case (k1KeyValue * 8) : return '1'; - default: return -1; + switch (idx) { + case CToI('r'): return 'r'; + case CToI('g'): return 'g'; + case CToI('b'): return 'b'; + case CToI('a'): return 'a'; + case CToI('0'): return '0'; + case CToI('1'): return '1'; + default: SkASSERT(false); } + return -1; } +constexpr GrSwizzle GrSwizzle::Concat(const GrSwizzle& a, const GrSwizzle& b) { + char swiz[4]{}; + for (int i = 0; i < 4; ++i) { + int idx = (b.fKey >> (4U * i)) & 0xfU; + switch (idx) { + case CToI('0'): swiz[i] = '0'; break; + case CToI('1'): swiz[i] = '1'; break; + default: swiz[i] = a.fSwiz[idx]; break; + } + } + return GrSwizzle(swiz); +} #endif diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp index 462ead8b62..3495383005 100644 --- a/src/gpu/gl/GrGLCaps.cpp +++ b/src/gpu/gl/GrGLCaps.cpp @@ -1333,13 +1333,6 @@ bool GrGLCaps::getExternalFormat(GrPixelConfig surfaceConfig, GrPixelConfig memo bool surfaceIsAlphaOnly = GrPixelConfigIsAlphaOnly(surfaceConfig); bool memoryIsAlphaOnly = GrPixelConfigIsAlphaOnly(memoryConfig); - // We don't currently support moving RGBA data into and out of ALPHA surfaces. It could be - // made to work. However, this is complicated by the use of GL_RED for alpha-only textures but - // is not needed currently. - if (surfaceIsAlphaOnly && !memoryIsAlphaOnly) { - return false; - } - *externalFormat = fConfigTable[memoryConfig].fFormats.fExternalFormat[usage]; *externalType = fConfigTable[memoryConfig].fFormats.fExternalType; @@ -1555,9 +1548,7 @@ void GrGLCaps::initConfigTable(const GrContextOptions& contextOptions, fConfigTable[kRGB_888_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RGB8; // Our external RGB data always has a byte where alpha would be. When calling read pixels we // want to read to kRGB_888x color type and ensure that gets 0xFF written. Using GL_RGB would - // read back unaligned 24bit RGB color values. Note that this all a bit moot as we don't - // currently expect to ever read back GrColorType::kRGB_888x because our implementation of - // supportedReadPixelsColorType never returns it. + // read back unaligned 24bit RGB color values. fConfigTable[kRGB_888_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RGBA; fConfigTable[kRGB_888_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE; fConfigTable[kRGB_888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType; @@ -1848,6 +1839,7 @@ void GrGLCaps::initConfigTable(const GrContextOptions& contextOptions, alphaInfo.fFormats.fSizedInternalFormat = GR_GL_ALPHA8; alphaInfo.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_ALPHA; shaderCaps->fConfigTextureSwizzle[kAlpha_8_as_Alpha_GrPixelConfig] = GrSwizzle::AAAA(); + alphaInfo.fRGBAReadSwizzle = GrSwizzle("000a"); if (fAlpha8IsRenderable && alpha8IsValidForGL) { alphaInfo.fFlags |= allRenderFlags; } @@ -1858,6 +1850,7 @@ void GrGLCaps::initConfigTable(const GrContextOptions& contextOptions, redInfo.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RED; redInfo.fFormats.fExternalType = GR_GL_UNSIGNED_BYTE; redInfo.fFormatType = kNormalizedFixedPoint_FormatType; + redInfo.fRGBAReadSwizzle = GrSwizzle("000r"); shaderCaps->fConfigTextureSwizzle[kAlpha_8_as_Red_GrPixelConfig] = GrSwizzle::RRRR(); // ES2 Command Buffer does not allow TexStorage with R8_EXT (so Alpha_8 and Gray_8) @@ -2042,6 +2035,7 @@ void GrGLCaps::initConfigTable(const GrContextOptions& contextOptions, redHalf.fFormats.fSizedInternalFormat = GR_GL_R16F; redHalf.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_RED; shaderCaps->fConfigTextureSwizzle[kAlpha_half_as_Red_GrPixelConfig] = GrSwizzle::RRRR(); + redHalf.fRGBAReadSwizzle = GrSwizzle("000r"); if (textureRedSupport && hasFP16Textures) { redHalf.fFlags = ConfigInfo::kTextureable_Flag; @@ -3033,30 +3027,29 @@ bool GrGLCaps::surfaceSupportsReadPixels(const GrSurface* surface) const { return true; } -GrColorType GrGLCaps::supportedReadPixelsColorType(GrPixelConfig config, - GrColorType dstColorType) const { +GrCaps::SupportedRead GrGLCaps::supportedReadPixelsColorType(GrPixelConfig srcPixelConfig, + const GrBackendFormat& srcFormat, + GrColorType dstColorType) const { // For now, we mostly report the read back format that is required by the ES spec without // checking for implementation allowed formats or consider laxer rules in non-ES GL. TODO: Relax // this as makes sense to increase performance and correctness. - switch (fConfigTable[config].fFormatType) { - case kNormalizedFixedPoint_FormatType: - if (kRGB_888X_GrPixelConfig == config) { - return GrColorType::kRGB_888x; - } - return GrColorType::kRGBA_8888; - case kFloat_FormatType: - if ((kAlpha_half_GrPixelConfig == config || - kAlpha_half_as_Red_GrPixelConfig == config) && - GrColorType::kAlpha_F16 == dstColorType) { - return GrColorType::kAlpha_F16; - } - // And similar for full float RG. - if (kRG_float_GrPixelConfig == config && GrColorType::kRG_F32 == dstColorType) { - return GrColorType::kRG_F32; - } - return GrColorType::kRGBA_F32; + const GrGLenum* glFormat = srcFormat.getGLFormat(); + if (!glFormat) { + return {GrSwizzle{}, GrColorType::kUnknown}; } - return GrColorType::kUnknown; + auto swizzle = fConfigTable[srcPixelConfig].fRGBAReadSwizzle; + switch (fConfigTable[srcPixelConfig].fFormatType) { + case kNormalizedFixedPoint_FormatType: + if (kRGB_888X_GrPixelConfig == srcPixelConfig && *glFormat == GR_GL_RGBA8 && + GrColorTypeHasAlpha(dstColorType)) { + // This can skip an unnecessary conversion. + return {swizzle, GrColorType::kRGB_888x}; + } + return {swizzle, GrColorType::kRGBA_8888}; + case kFloat_FormatType: + return {swizzle, GrColorType::kRGBA_F32}; + } + return {GrSwizzle{}, GrColorType::kUnknown}; } bool GrGLCaps::onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget& backendRT) const { diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h index fe1a9f2596..54cc1d9943 100644 --- a/src/gpu/gl/GrGLCaps.h +++ b/src/gpu/gl/GrGLCaps.h @@ -307,7 +307,8 @@ public: bool useNonVBOVertexAndIndexDynamicData() const { return fUseNonVBOVertexAndIndexDynamicData; } bool surfaceSupportsReadPixels(const GrSurface*) const override; - GrColorType supportedReadPixelsColorType(GrPixelConfig, GrColorType) const override; + SupportedRead supportedReadPixelsColorType(GrPixelConfig, const GrBackendFormat&, + GrColorType) const override; /// Does ReadPixels support reading readConfig pixels from a FBO that is surfaceConfig? bool readPixelsSupported(GrPixelConfig surfaceConfig, @@ -568,6 +569,11 @@ private: // Index fStencilFormats. int fStencilFormatIndex; + // If data from a surface of this config is read back to a GrColorType with all four + // color channels this indicates how each channel should be interpreted. May contain + // 0s and 1s. + GrSwizzle fRGBAReadSwizzle = GrSwizzle("rgba"); + SkTDArray fColorSampleCounts; enum { diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp index 811b7188fb..8aa6bb48f1 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -2973,6 +2973,7 @@ static void get_gl_swizzle_values(const GrSwizzle& swizzle, GrGLenum glValues[4] case 'g': glValues[i] = GR_GL_GREEN; break; case 'b': glValues[i] = GR_GL_BLUE; break; case 'a': glValues[i] = GR_GL_ALPHA; break; + case '0': glValues[i] = GR_GL_ZERO; break; case '1': glValues[i] = GR_GL_ONE; break; default: SK_ABORT("Unsupported component"); } diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h index 73cc6d0f2d..9b24c859b6 100644 --- a/src/opts/SkRasterPipeline_opts.h +++ b/src/opts/SkRasterPipeline_opts.h @@ -2636,6 +2636,7 @@ STAGE(swizzle, void* ctx) { case 'g': *o[i] = ig; break; case 'b': *o[i] = ib; break; case 'a': *o[i] = ia; break; + case '0': *o[i] = F(0); break; case '1': *o[i] = F(1); break; default: break; } @@ -3441,7 +3442,7 @@ SI void store_88_(uint16_t* ptr, size_t tail, U16 r, U16 g) { STAGE_PP(load_rg88, const SkRasterPipeline_MemoryCtx* ctx) { b = 0; - a = 1; + a = 255; load_88_(ptr_at_xy(ctx, dx,dy), tail, &r,&g); } STAGE_PP(store_rg88, const SkRasterPipeline_MemoryCtx* ctx) { @@ -3832,6 +3833,7 @@ STAGE_PP(swizzle, void* ctx) { case 'g': *o[i] = ig; break; case 'b': *o[i] = ib; break; case 'a': *o[i] = ia; break; + case '0': *o[i] = U16(0); break; case '1': *o[i] = U16(255); break; default: break; } diff --git a/tests/GrTestingBackendTextureUploadTest.cpp b/tests/GrTestingBackendTextureUploadTest.cpp index 50362071be..8259884f24 100644 --- a/tests/GrTestingBackendTextureUploadTest.cpp +++ b/tests/GrTestingBackendTextureUploadTest.cpp @@ -37,9 +37,6 @@ void testing_only_texture_test(skiatest::Reporter* reporter, GrContext* context, return; } - if (caps->supportedReadPixelsColorType(config, grCT) != grCT) { - return; - } GrBackendTexture backendTex; @@ -61,6 +58,12 @@ void testing_only_texture_test(skiatest::Reporter* reporter, GrContext* context, if (!backendTex.isValid()) { return; } + // skbug.com/9165 + auto supportedRead = + caps->supportedReadPixelsColorType(config, backendTex.getBackendFormat(), grCT); + if (supportedRead.fColorType != grCT || supportedRead.fSwizzle != GrSwizzle("rgba")) { + return; + } sk_sp wrappedProxy; if (GrRenderable::kYes == renderable) { diff --git a/tests/SkRasterPipelineTest.cpp b/tests/SkRasterPipelineTest.cpp index ce4fcf4315..37c51ae909 100644 --- a/tests/SkRasterPipelineTest.cpp +++ b/tests/SkRasterPipelineTest.cpp @@ -526,7 +526,7 @@ DEF_TEST(SkRasterPipeline_swizzle, r) { rg[i][1] = 2 * i + 1; } - GrSwizzle swizzle("1gra"); + GrSwizzle swizzle("0gra"); uint16_t buffer[64][4]; SkRasterPipeline_MemoryCtx src = { rg, 0 }, @@ -539,7 +539,7 @@ DEF_TEST(SkRasterPipeline_swizzle, r) { for (int i = 0; i < 64; i++) { uint16_t want[4] { - h(1), + h(0), h(2 * i + 1), h(i + 1), h(1),