Home | History | Annotate | Download | only in shaders
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkArenaAlloc.h"
      9 #include "SkBitmapController.h"
     10 #include "SkBitmapProcShader.h"
     11 #include "SkBitmapProvider.h"
     12 #include "SkColorSpacePriv.h"
     13 #include "SkColorSpaceXformSteps.h"
     14 #include "SkEmptyShader.h"
     15 #include "SkImage_Base.h"
     16 #include "SkImageShader.h"
     17 #include "SkReadBuffer.h"
     18 #include "SkWriteBuffer.h"
     19 
     20 /**
     21  *  We are faster in clamp, so always use that tiling when we can.
     22  */
     23 static SkShader::TileMode optimize(SkShader::TileMode tm, int dimension) {
     24     SkASSERT(dimension > 0);
     25 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
     26     // need to update frameworks/base/libs/hwui/tests/unit/SkiaBehaviorTests.cpp:55 to allow
     27     // for transforming to clamp.
     28     return tm;
     29 #else
     30     return dimension == 1 ? SkShader::kClamp_TileMode : tm;
     31 #endif
     32 }
     33 
     34 SkImageShader::SkImageShader(sk_sp<SkImage> img,
     35                              TileMode tmx, TileMode tmy,
     36                              const SkMatrix* localMatrix,
     37                              bool clampAsIfUnpremul)
     38     : INHERITED(localMatrix)
     39     , fImage(std::move(img))
     40     , fTileModeX(optimize(tmx, fImage->width()))
     41     , fTileModeY(optimize(tmy, fImage->height()))
     42     , fClampAsIfUnpremul(clampAsIfUnpremul)
     43 {}
     44 
     45 // fClampAsIfUnpremul is always false when constructed through public APIs,
     46 // so there's no need to read or write it here.
     47 
     48 sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
     49     const TileMode tx = (TileMode)buffer.readUInt();
     50     const TileMode ty = (TileMode)buffer.readUInt();
     51     SkMatrix localMatrix;
     52     buffer.readMatrix(&localMatrix);
     53     sk_sp<SkImage> img = buffer.readImage();
     54     if (!img) {
     55         return nullptr;
     56     }
     57     return SkImageShader::Make(std::move(img), tx, ty, &localMatrix);
     58 }
     59 
     60 void SkImageShader::flatten(SkWriteBuffer& buffer) const {
     61     buffer.writeUInt(fTileModeX);
     62     buffer.writeUInt(fTileModeY);
     63     buffer.writeMatrix(this->getLocalMatrix());
     64     buffer.writeImage(fImage.get());
     65     SkASSERT(fClampAsIfUnpremul == false);
     66 }
     67 
     68 bool SkImageShader::isOpaque() const {
     69     return fImage->isOpaque() && fTileModeX != kDecal_TileMode && fTileModeY != kDecal_TileMode;
     70 }
     71 
     72 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
     73 static bool legacy_shader_can_handle(const SkMatrix& inv) {
     74     if (!inv.isScaleTranslate()) {
     75         return false;
     76     }
     77 
     78     // legacy code uses SkFixed 32.32, so ensure the inverse doesn't map device coordinates
     79     // out of range.
     80     const SkScalar max_dev_coord = 32767.0f;
     81     SkRect src;
     82     SkAssertResult(inv.mapRect(&src, SkRect::MakeWH(max_dev_coord, max_dev_coord)));
     83 
     84     // take 1/4 of max signed 32bits so we have room to subtract local values
     85     const SkScalar max_fixed32dot32 = SK_MaxS32 * 0.25f;
     86     if (!SkRect::MakeLTRB(-max_fixed32dot32, -max_fixed32dot32,
     87                            max_fixed32dot32, max_fixed32dot32).contains(src)) {
     88         return false;
     89     }
     90 
     91     // legacy shader impl should be able to handle these matrices
     92     return true;
     93 }
     94 
     95 SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
     96                                                     SkArenaAlloc* alloc) const {
     97     if (fImage->alphaType() == kUnpremul_SkAlphaType) {
     98         return nullptr;
     99     }
    100     if (fImage->colorType() != kN32_SkColorType) {
    101         return nullptr;
    102     }
    103     if (fTileModeX != fTileModeY) {
    104         return nullptr;
    105     }
    106     if (fTileModeX == kDecal_TileMode || fTileModeY == kDecal_TileMode) {
    107         return nullptr;
    108     }
    109 
    110     // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer,
    111     // so it can't handle bitmaps larger than 65535.
    112     //
    113     // We back off another bit to 32767 to make small amounts of
    114     // intermediate math safe, e.g. in
    115     //
    116     //     SkFixed fx = ...;
    117     //     fx = tile(fx + SK_Fixed1);
    118     //
    119     // we want to make sure (fx + SK_Fixed1) never overflows.
    120     if (fImage-> width() > 32767 ||
    121         fImage->height() > 32767) {
    122         return nullptr;
    123     }
    124 
    125     SkMatrix inv;
    126     if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &inv) ||
    127         !legacy_shader_can_handle(inv)) {
    128         return nullptr;
    129     }
    130 
    131     if (!rec.isLegacyCompatible(fImage->colorSpace())) {
    132         return nullptr;
    133     }
    134 
    135     return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY,
    136                                                  SkBitmapProvider(fImage.get()), rec, alloc);
    137 }
    138 #endif
    139 
    140 SkImage* SkImageShader::onIsAImage(SkMatrix* texM, TileMode xy[]) const {
    141     if (texM) {
    142         *texM = this->getLocalMatrix();
    143     }
    144     if (xy) {
    145         xy[0] = (TileMode)fTileModeX;
    146         xy[1] = (TileMode)fTileModeY;
    147     }
    148     return const_cast<SkImage*>(fImage.get());
    149 }
    150 
    151 sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
    152                                     TileMode tx, TileMode ty,
    153                                     const SkMatrix* localMatrix,
    154                                     bool clampAsIfUnpremul) {
    155     if (!image) {
    156         return sk_make_sp<SkEmptyShader>();
    157     }
    158     return sk_sp<SkShader>{ new SkImageShader(image, tx,ty, localMatrix, clampAsIfUnpremul) };
    159 }
    160 
    161 ///////////////////////////////////////////////////////////////////////////////////////////////////
    162 
    163 #if SK_SUPPORT_GPU
    164 
    165 #include "GrCaps.h"
    166 #include "GrColorSpaceInfo.h"
    167 #include "GrRecordingContext.h"
    168 #include "GrRecordingContextPriv.h"
    169 #include "SkGr.h"
    170 #include "effects/GrBicubicEffect.h"
    171 #include "effects/GrSimpleTextureEffect.h"
    172 
    173 static GrSamplerState::WrapMode tile_mode_to_wrap_mode(const SkShader::TileMode tileMode) {
    174     switch (tileMode) {
    175         case SkShader::TileMode::kClamp_TileMode:
    176             return GrSamplerState::WrapMode::kClamp;
    177         case SkShader::TileMode::kRepeat_TileMode:
    178             return GrSamplerState::WrapMode::kRepeat;
    179         case SkShader::TileMode::kMirror_TileMode:
    180             return GrSamplerState::WrapMode::kMirrorRepeat;
    181         case SkShader::kDecal_TileMode:
    182             return GrSamplerState::WrapMode::kClampToBorder;
    183     }
    184     SK_ABORT("Unknown tile mode.");
    185     return GrSamplerState::WrapMode::kClamp;
    186 }
    187 
    188 std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
    189         const GrFPArgs& args) const {
    190     const auto lm = this->totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix);
    191     SkMatrix lmInverse;
    192     if (!lm->invert(&lmInverse)) {
    193         return nullptr;
    194     }
    195 
    196     GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX),
    197                                             tile_mode_to_wrap_mode(fTileModeY)};
    198 
    199     // If either domainX or domainY are un-ignored, a texture domain effect has to be used to
    200     // implement the decal mode (while leaving non-decal axes alone). The wrap mode originally
    201     // clamp-to-border is reset to clamp since the hw cannot implement it directly.
    202     GrTextureDomain::Mode domainX = GrTextureDomain::kIgnore_Mode;
    203     GrTextureDomain::Mode domainY = GrTextureDomain::kIgnore_Mode;
    204     if (!args.fContext->priv().caps()->clampToBorderSupport()) {
    205         if (wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder) {
    206             domainX = GrTextureDomain::kDecal_Mode;
    207             wrapModes[0] = GrSamplerState::WrapMode::kClamp;
    208         }
    209         if (wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) {
    210             domainY = GrTextureDomain::kDecal_Mode;
    211             wrapModes[1] = GrSamplerState::WrapMode::kClamp;
    212         }
    213     }
    214 
    215     // Must set wrap and filter on the sampler before requesting a texture. In two places below
    216     // we check the matrix scale factors to determine how to interpret the filter quality setting.
    217     // This completely ignores the complexity of the drawVertices case where explicit local coords
    218     // are provided by the caller.
    219     bool doBicubic;
    220     GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode(
    221             args.fFilterQuality, *args.fViewMatrix, *lm,
    222             args.fContext->priv().options().fSharpenMipmappedTextures, &doBicubic);
    223     GrSamplerState samplerState(wrapModes, textureFilterMode);
    224     SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
    225     sk_sp<GrTextureProxy> proxy(as_IB(fImage)->asTextureProxyRef(args.fContext, samplerState,
    226                                                                  scaleAdjust));
    227     if (!proxy) {
    228         return nullptr;
    229     }
    230 
    231     GrPixelConfig config = proxy->config();
    232     bool isAlphaOnly = GrPixelConfigIsAlphaOnly(config);
    233 
    234     lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]);
    235 
    236     std::unique_ptr<GrFragmentProcessor> inner;
    237     if (doBicubic) {
    238         // domainX and domainY will properly apply the decal effect with the texture domain used in
    239         // the bicubic filter if clamp to border was unsupported in hardware
    240         inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes, domainX, domainY);
    241     } else {
    242         if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
    243             SkRect domain = GrTextureDomain::MakeTexelDomain(
    244                     SkIRect::MakeWH(proxy->width(), proxy->height()),
    245                     domainX, domainY);
    246             inner = GrTextureDomainEffect::Make(std::move(proxy), lmInverse, domain,
    247                                                 domainX, domainY, samplerState);
    248         } else {
    249             inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
    250         }
    251     }
    252     inner = GrColorSpaceXformEffect::Make(std::move(inner), fImage->colorSpace(),
    253                                           fImage->alphaType(),
    254                                           args.fDstColorSpaceInfo->colorSpace());
    255     if (isAlphaOnly) {
    256         return inner;
    257     } else if (args.fInputColorIsOpaque) {
    258         return GrFragmentProcessor::OverrideInput(std::move(inner), SK_PMColor4fWHITE, false);
    259     }
    260     return GrFragmentProcessor::MulChildByInputAlpha(std::move(inner));
    261 }
    262 
    263 #endif
    264 
    265 ///////////////////////////////////////////////////////////////////////////////////////////////////
    266 #include "SkImagePriv.h"
    267 
    268 sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode tmx,
    269                                    SkShader::TileMode tmy, const SkMatrix* localMatrix,
    270                                    SkCopyPixelsMode cpm) {
    271     return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm),
    272                                tmx, tmy, localMatrix);
    273 }
    274 
    275 void SkShaderBase::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkImageShader); }
    276 
    277 bool SkImageShader::onAppendStages(const StageRec& rec) const {
    278     SkRasterPipeline* p = rec.fPipeline;
    279     SkArenaAlloc* alloc = rec.fAlloc;
    280 
    281     SkMatrix matrix;
    282     if (!this->computeTotalInverse(rec.fCTM, rec.fLocalM, &matrix)) {
    283         return false;
    284     }
    285     auto quality = rec.fPaint.getFilterQuality();
    286 
    287     SkBitmapProvider provider(fImage.get());
    288     const auto* state = SkBitmapController::RequestBitmap(provider, matrix, quality, alloc);
    289     if (!state) {
    290         return false;
    291     }
    292 
    293     const SkPixmap& pm = state->pixmap();
    294     matrix  = state->invMatrix();
    295     quality = state->quality();
    296     auto info = pm.info();
    297 
    298     // When the matrix is just an integer translate, bilerp == nearest neighbor.
    299     if (quality == kLow_SkFilterQuality &&
    300         matrix.getType() <= SkMatrix::kTranslate_Mask &&
    301         matrix.getTranslateX() == (int)matrix.getTranslateX() &&
    302         matrix.getTranslateY() == (int)matrix.getTranslateY()) {
    303         quality = kNone_SkFilterQuality;
    304     }
    305 
    306     // See skia:4649 and the GM image_scale_aligned.
    307     if (quality == kNone_SkFilterQuality) {
    308         if (matrix.getScaleX() >= 0) {
    309             matrix.setTranslateX(nextafterf(matrix.getTranslateX(),
    310                                             floorf(matrix.getTranslateX())));
    311         }
    312         if (matrix.getScaleY() >= 0) {
    313             matrix.setTranslateY(nextafterf(matrix.getTranslateY(),
    314                                             floorf(matrix.getTranslateY())));
    315         }
    316     }
    317 
    318     p->append(SkRasterPipeline::seed_shader);
    319     p->append_matrix(alloc, matrix);
    320 
    321     auto gather = alloc->make<SkRasterPipeline_GatherCtx>();
    322     gather->pixels = pm.addr();
    323     gather->stride = pm.rowBytesAsPixels();
    324     gather->width  = pm.width();
    325     gather->height = pm.height();
    326 
    327     auto limit_x = alloc->make<SkRasterPipeline_TileCtx>(),
    328          limit_y = alloc->make<SkRasterPipeline_TileCtx>();
    329     limit_x->scale = pm.width();
    330     limit_x->invScale = 1.0f / pm.width();
    331     limit_y->scale = pm.height();
    332     limit_y->invScale = 1.0f / pm.height();
    333 
    334     SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
    335     bool decal_x_and_y = fTileModeX == kDecal_TileMode && fTileModeY == kDecal_TileMode;
    336     if (fTileModeX == kDecal_TileMode || fTileModeY == kDecal_TileMode) {
    337         decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
    338         decal_ctx->limit_x = limit_x->scale;
    339         decal_ctx->limit_y = limit_y->scale;
    340     }
    341 
    342     auto append_tiling_and_gather = [&] {
    343         if (decal_x_and_y) {
    344             p->append(SkRasterPipeline::decal_x_and_y,  decal_ctx);
    345         } else {
    346             switch (fTileModeX) {
    347                 case kClamp_TileMode:  /* The gather_xxx stage will clamp for us. */     break;
    348                 case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x);   break;
    349                 case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x);   break;
    350                 case kDecal_TileMode:  p->append(SkRasterPipeline::decal_x,  decal_ctx); break;
    351             }
    352             switch (fTileModeY) {
    353                 case kClamp_TileMode:  /* The gather_xxx stage will clamp for us. */     break;
    354                 case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y);   break;
    355                 case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y);   break;
    356                 case kDecal_TileMode:  p->append(SkRasterPipeline::decal_y,  decal_ctx); break;
    357             }
    358         }
    359 
    360         void* ctx = gather;
    361         switch (info.colorType()) {
    362             case kAlpha_8_SkColorType:      p->append(SkRasterPipeline::gather_a8,      ctx); break;
    363             case kRGB_565_SkColorType:      p->append(SkRasterPipeline::gather_565,     ctx); break;
    364             case kARGB_4444_SkColorType:    p->append(SkRasterPipeline::gather_4444,    ctx); break;
    365             case kRGBA_8888_SkColorType:    p->append(SkRasterPipeline::gather_8888,    ctx); break;
    366             case kRGBA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); break;
    367             case kRGBA_F16Norm_SkColorType:
    368             case kRGBA_F16_SkColorType:     p->append(SkRasterPipeline::gather_f16,     ctx); break;
    369             case kRGBA_F32_SkColorType:     p->append(SkRasterPipeline::gather_f32,     ctx); break;
    370 
    371             case kGray_8_SkColorType:       p->append(SkRasterPipeline::gather_a8,      ctx);
    372                                             p->append(SkRasterPipeline::alpha_to_gray      ); break;
    373 
    374             case kRGB_888x_SkColorType:     p->append(SkRasterPipeline::gather_8888,    ctx);
    375                                             p->append(SkRasterPipeline::force_opaque       ); break;
    376 
    377             case kRGB_101010x_SkColorType:  p->append(SkRasterPipeline::gather_1010102, ctx);
    378                                             p->append(SkRasterPipeline::force_opaque       ); break;
    379 
    380             case kBGRA_8888_SkColorType:    p->append(SkRasterPipeline::gather_8888,    ctx);
    381                                             p->append(SkRasterPipeline::swap_rb            ); break;
    382 
    383             case kUnknown_SkColorType: SkASSERT(false);
    384         }
    385         if (decal_ctx) {
    386             p->append(SkRasterPipeline::check_decal_mask, decal_ctx);
    387         }
    388     };
    389 
    390     auto append_misc = [&] {
    391         // TODO: if ref.fDstCS isn't null, we'll premul here then immediately unpremul
    392         // to do the color space transformation.  Might be possible to streamline.
    393         if (info.colorType() == kAlpha_8_SkColorType) {
    394             // The color for A8 images comes from the (sRGB) paint color.
    395             p->append_set_rgb(alloc, rec.fPaint.getColor4f());
    396             p->append(SkRasterPipeline::premul);
    397         } else if (info.alphaType() == kUnpremul_SkAlphaType) {
    398             // Convert unpremul images to premul before we carry on with the rest of the pipeline.
    399             p->append(SkRasterPipeline::premul);
    400         }
    401 
    402         if (quality > kLow_SkFilterQuality) {
    403             // Bicubic filtering naturally produces out of range values on both sides.
    404             p->append(SkRasterPipeline::clamp_0);
    405             p->append(fClampAsIfUnpremul ? SkRasterPipeline::clamp_1
    406                                          : SkRasterPipeline::clamp_a);
    407         }
    408 
    409         if (rec.fDstCS) {
    410             // If color managed, convert from premul source all the way to premul dst color space.
    411             auto srcCS = info.colorSpace();
    412             if (!srcCS || info.colorType() == kAlpha_8_SkColorType) {
    413                 // We treat untagged images as sRGB.
    414                 // A8 images get their r,g,b from the paint color, so they're also sRGB.
    415                 srcCS = sk_srgb_singleton();
    416             }
    417             alloc->make<SkColorSpaceXformSteps>(srcCS     , kPremul_SkAlphaType,
    418                                                 rec.fDstCS, kPremul_SkAlphaType)
    419                 ->apply(p, info.colorType());
    420         }
    421 
    422         return true;
    423     };
    424 
    425     // We've got a fast path for 8888 bilinear clamp/clamp sampling.
    426     auto ct = info.colorType();
    427     if (true
    428         && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType)
    429         && quality == kLow_SkFilterQuality
    430         && fTileModeX == SkShader::kClamp_TileMode
    431         && fTileModeY == SkShader::kClamp_TileMode) {
    432 
    433         p->append(SkRasterPipeline::bilerp_clamp_8888, gather);
    434         if (ct == kBGRA_8888_SkColorType) {
    435             p->append(SkRasterPipeline::swap_rb);
    436         }
    437         return append_misc();
    438     }
    439 
    440     SkRasterPipeline_SamplerCtx* sampler = nullptr;
    441     if (quality != kNone_SkFilterQuality) {
    442         sampler = alloc->make<SkRasterPipeline_SamplerCtx>();
    443     }
    444 
    445     auto sample = [&](SkRasterPipeline::StockStage setup_x,
    446                       SkRasterPipeline::StockStage setup_y) {
    447         p->append(setup_x, sampler);
    448         p->append(setup_y, sampler);
    449         append_tiling_and_gather();
    450         p->append(SkRasterPipeline::accumulate, sampler);
    451     };
    452 
    453     if (quality == kNone_SkFilterQuality) {
    454         append_tiling_and_gather();
    455 
    456     } else if (quality == kLow_SkFilterQuality) {
    457         p->append(SkRasterPipeline::save_xy, sampler);
    458 
    459         sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny);
    460         sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny);
    461         sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py);
    462         sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py);
    463 
    464         p->append(SkRasterPipeline::move_dst_src);
    465 
    466     } else {
    467         p->append(SkRasterPipeline::save_xy, sampler);
    468 
    469         sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y);
    470         sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y);
    471         sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y);
    472         sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y);
    473 
    474         sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y);
    475         sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y);
    476         sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y);
    477         sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y);
    478 
    479         sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y);
    480         sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y);
    481         sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y);
    482         sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y);
    483 
    484         sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y);
    485         sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y);
    486         sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y);
    487         sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y);
    488 
    489         p->append(SkRasterPipeline::move_dst_src);
    490     }
    491 
    492     return append_misc();
    493 }
    494