Add function to GrDataUtils to handle color conversions.
Like SkConvertPixels but knows about all GrColorTypes, origin, and can
apply an arbitrary GrSwizzle.
Use in GrSurfaceContext read/write pixels methods.
Add support for '0' to GrSwizzle.
Change-Id: Ib9dd215fcb0ee8b33c4020893c22b4ab7ce1f40b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/220761
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index adcedb6..ffacbc5 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -348,3 +348,9 @@
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 5d5d3cc..2a25bc5 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -205,18 +205,26 @@
* 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 965251e..abfe351 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 @@
}
}
+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<SkColorSpaceXformSteps> 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<void*>(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<char*>(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<char*>(srcCtx.pixels) - srcInfo.fRowBytes;
+ dstCtx.pixels = static_cast<char*>(dstCtx.pixels) + dstInfo.fRowBytes;
+ }
+ return true;
+}
diff --git a/src/gpu/GrDataUtils.h b/src/gpu/GrDataUtils.h
index b79e84c..fcf7a07 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 @@
const SkTArray<size_t>& 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 597631c..6c8c342 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 @@
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 @@
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 @@
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 71113b4..4b29ce0 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 @@
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<char[]> 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<char[]> row(new char[trimRowBytes]);
- char* upper = reinterpret_cast<char*>(buffer);
- char* lower = reinterpret_cast<char*>(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 @@
// 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 @@
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<char[]> tempBuffer;
+ std::unique_ptr<char[]> 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<char[]> 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<char*>(tempBuffer.get()) + trimRowBytes * (height - 1);
- const char* src = reinterpret_cast<const char*>(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 21fe834..138b24f 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 cbee3eb..991379c 100644
--- a/src/gpu/GrSwizzle.h
+++ b/src/gpu/GrSwizzle.h
@@ -22,15 +22,18 @@
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 @@
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 @@
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 462ead8..3495383 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1333,13 +1333,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 fe1a9f2..54cc1d9 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -307,7 +307,8 @@
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 @@
// 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<int> fColorSampleCounts;
enum {
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 811b718..8aa6bb4 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -2973,6 +2973,7 @@
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 73cc6d0..9b24c85 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -2636,6 +2636,7 @@
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 @@
STAGE_PP(load_rg88, const SkRasterPipeline_MemoryCtx* ctx) {
b = 0;
- a = 1;
+ a = 255;
load_88_(ptr_at_xy<const uint16_t>(ctx, dx,dy), tail, &r,&g);
}
STAGE_PP(store_rg88, const SkRasterPipeline_MemoryCtx* ctx) {
@@ -3832,6 +3833,7 @@
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;
}