| /* | |
| * Copyright 2019 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/private/SkImageInfoPriv.h" | |
| #include "include/private/SkMacros.h" | |
| #include "src/core/SkArenaAlloc.h" | |
| #include "src/core/SkBlendModePriv.h" | |
| #include "src/core/SkBlenderBase.h" | |
| #include "src/core/SkColorFilterBase.h" | |
| #include "src/core/SkColorSpacePriv.h" | |
| #include "src/core/SkColorSpaceXformSteps.h" | |
| #include "src/core/SkCoreBlitters.h" | |
| #include "src/core/SkLRUCache.h" | |
| #include "src/core/SkMatrixProvider.h" | |
| #include "src/core/SkOpts.h" | |
| #include "src/core/SkPaintPriv.h" | |
| #include "src/core/SkVM.h" | |
| #include "src/core/SkVMBlitter.h" | |
| #include "src/shaders/SkColorFilterShader.h" | |
| #include <cinttypes> | |
| namespace { | |
| // Uniforms set by the Blitter itself, | |
| // rather than by the Shader, which follow this struct in the skvm::Uniforms buffer. | |
| struct BlitterUniforms { | |
| int right; // First device x + blit run length n, used to get device x coordinate. | |
| int y; // Device y coordinate. | |
| }; | |
| static_assert(SkIsAlign4(sizeof(BlitterUniforms)), ""); | |
| static constexpr int kBlitterUniformsCount = sizeof(BlitterUniforms) / 4; | |
| static skvm::Coord device_coord(skvm::Builder* p, skvm::Uniforms* uniforms) { | |
| skvm::I32 dx = p->uniform32(uniforms->base, offsetof(BlitterUniforms, right)) | |
| - p->index(), | |
| dy = p->uniform32(uniforms->base, offsetof(BlitterUniforms, y)); | |
| return { | |
| to_F32(dx) + 0.5f, | |
| to_F32(dy) + 0.5f, | |
| }; | |
| } | |
| struct NoopColorFilter : public SkColorFilterBase { | |
| skvm::Color onProgram(skvm::Builder*, skvm::Color c, | |
| const SkColorInfo&, skvm::Uniforms*, SkArenaAlloc*) const override { | |
| return c; | |
| } | |
| bool onAppendStages(const SkStageRec&, bool) const override { return true; } | |
| // Only created here, should never be flattened / unflattened. | |
| Factory getFactory() const override { return nullptr; } | |
| const char* getTypeName() const override { return "NoopColorFilter"; } | |
| }; | |
| struct SpriteShader : public SkShaderBase { | |
| explicit SpriteShader(SkPixmap sprite) : fSprite(sprite) {} | |
| SkPixmap fSprite; | |
| // Only created here temporarily... never serialized. | |
| Factory getFactory() const override { return nullptr; } | |
| const char* getTypeName() const override { return "SpriteShader"; } | |
| bool isOpaque() const override { return fSprite.isOpaque(); } | |
| skvm::Color onProgram(skvm::Builder* p, | |
| skvm::Coord /*device*/, skvm::Coord /*local*/, skvm::Color /*paint*/, | |
| const SkMatrixProvider&, const SkMatrix* /*localM*/, | |
| const SkColorInfo& dst, | |
| skvm::Uniforms* uniforms, SkArenaAlloc*) const override { | |
| const SkColorType ct = fSprite.colorType(); | |
| skvm::PixelFormat fmt = skvm::SkColorType_to_PixelFormat(ct); | |
| skvm::Color c = p->load(fmt, p->varying(SkColorTypeBytesPerPixel(ct))); | |
| return SkColorSpaceXformSteps{fSprite, dst}.program(p, uniforms, c); | |
| } | |
| }; | |
| struct DitherShader : public SkShaderBase { | |
| explicit DitherShader(sk_sp<SkShader> shader) : fShader(std::move(shader)) {} | |
| sk_sp<SkShader> fShader; | |
| // Only created here temporarily... never serialized. | |
| Factory getFactory() const override { return nullptr; } | |
| const char* getTypeName() const override { return "DitherShader"; } | |
| bool isOpaque() const override { return fShader->isOpaque(); } | |
| skvm::Color onProgram(skvm::Builder* p, | |
| skvm::Coord device, skvm::Coord local, skvm::Color paint, | |
| const SkMatrixProvider& matrices, const SkMatrix* localM, | |
| const SkColorInfo& dst, | |
| skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const override { | |
| // Run our wrapped shader. | |
| skvm::Color c = as_SB(fShader)->program(p, device,local, paint, | |
| matrices,localM, dst, uniforms,alloc); | |
| if (!c) { | |
| return {}; | |
| } | |
| float rate = 0.0f; | |
| switch (dst.colorType()) { | |
| case kARGB_4444_SkColorType: rate = 1/15.0f; break; | |
| case kRGB_565_SkColorType: rate = 1/63.0f; break; | |
| case kGray_8_SkColorType: | |
| case kRGB_888x_SkColorType: | |
| case kRGBA_8888_SkColorType: | |
| case kBGRA_8888_SkColorType: rate = 1/255.0f; break; | |
| case kRGB_101010x_SkColorType: | |
| case kRGBA_1010102_SkColorType: | |
| case kBGR_101010x_SkColorType: | |
| case kBGRA_1010102_SkColorType: rate = 1/1023.0f; break; | |
| case kUnknown_SkColorType: | |
| case kAlpha_8_SkColorType: | |
| case kRGBA_F16_SkColorType: | |
| case kRGBA_F16Norm_SkColorType: | |
| case kRGBA_F32_SkColorType: | |
| case kR8G8_unorm_SkColorType: | |
| case kA16_float_SkColorType: | |
| case kA16_unorm_SkColorType: | |
| case kR16G16_float_SkColorType: | |
| case kR16G16_unorm_SkColorType: | |
| case kR16G16B16A16_unorm_SkColorType: return c; | |
| } | |
| // See SkRasterPipeline dither stage. | |
| // This is 8x8 ordered dithering. From here we'll only need dx and dx^dy. | |
| SkASSERT(local.x.id == device.x.id); | |
| SkASSERT(local.y.id == device.y.id); | |
| skvm::I32 X = trunc(device.x - 0.5f), | |
| Y = X ^ trunc(device.y - 0.5f); | |
| // If X's low bits are abc and Y's def, M is fcebda, | |
| // 6 bits producing all values [0,63] shuffled over an 8x8 grid. | |
| skvm::I32 M = shl(Y & 1, 5) | |
| | shl(X & 1, 4) | |
| | shl(Y & 2, 2) | |
| | shl(X & 2, 1) | |
| | shr(Y & 4, 1) | |
| | shr(X & 4, 2); | |
| // Scale to [0,1) by /64, then to (-0.5,0.5) using 63/128 (~0.492) as 0.5-ε, | |
| // and finally scale all that by rate. We keep dither strength strictly | |
| // within ±0.5 to not change exact values like 0 or 1. | |
| // rate could be a uniform, but since it's based on the destination SkColorType, | |
| // we can bake it in without hurting the cache hit rate. | |
| float scale = rate * ( 2/128.0f), | |
| bias = rate * (-63/128.0f); | |
| skvm::F32 dither = to_F32(M) * scale + bias; | |
| c.r += dither; | |
| c.g += dither; | |
| c.b += dither; | |
| c.r = clamp(c.r, 0.0f, c.a); | |
| c.g = clamp(c.g, 0.0f, c.a); | |
| c.b = clamp(c.b, 0.0f, c.a); | |
| return c; | |
| } | |
| }; | |
| // This is similar to using SkShaders::Color(paint.getColor4f(), nullptr), | |
| // but uses the blitter-provided paint color uniforms instead of pushing its own. | |
| struct PaintColorShader : public SkShaderBase { | |
| explicit PaintColorShader(bool isOpaque) : fIsOpaque(isOpaque) {} | |
| const bool fIsOpaque; | |
| // Only created here temporarily... never serialized. | |
| Factory getFactory() const override { return nullptr; } | |
| const char* getTypeName() const override { return "PaintColorShader"; } | |
| bool isOpaque() const override { return fIsOpaque; } | |
| skvm::Color onProgram(skvm::Builder*, | |
| skvm::Coord, skvm::Coord, skvm::Color paint, | |
| const SkMatrixProvider&, const SkMatrix*, const SkColorInfo&, | |
| skvm::Uniforms*, SkArenaAlloc*) const override { | |
| // Incoming `paint` is unpremul in the destination color space, | |
| // so we just need to premul it. | |
| return premul(paint); | |
| } | |
| }; | |
| } // namespace | |
| bool SkVMBlitter::Key::operator==(const Key& that) const { | |
| return this->shader == that.shader | |
| && this->clip == that.clip | |
| && this->blender == that.blender | |
| && this->colorSpace == that.colorSpace | |
| && this->colorType == that.colorType | |
| && this->alphaType == that.alphaType | |
| && this->coverage == that.coverage; | |
| } | |
| SkVMBlitter::Key SkVMBlitter::Key::withCoverage(Coverage c) const { | |
| Key k = *this; | |
| k.coverage = SkToU8(c); | |
| return k; | |
| } | |
| SkVMBlitter::Params SkVMBlitter::Params::withCoverage(Coverage c) const { | |
| Params p = *this; | |
| p.coverage = c; | |
| return p; | |
| } | |
| SkVMBlitter::Params SkVMBlitter::EffectiveParams(const SkPixmap& device, | |
| const SkPixmap* sprite, | |
| SkPaint paint, | |
| const SkMatrixProvider& matrices, | |
| sk_sp<SkShader> clip) { | |
| // Sprites take priority over any shader. (There's rarely one set, and it's meaningless.) | |
| if (sprite) { | |
| paint.setShader(sk_make_sp<SpriteShader>(*sprite)); | |
| } | |
| // Normal blitters will have already folded color filters into their shader, | |
| // but we may still need to do that here for SpriteShaders. | |
| if (paint.getColorFilter()) { | |
| SkPaintPriv::RemoveColorFilter(&paint, device.colorSpace()); | |
| } | |
| SkASSERT(!paint.getColorFilter()); | |
| // If there's no explicit shader, the paint color is the shader, | |
| // but if there is a shader, it's modulated by the paint alpha. | |
| sk_sp<SkShader> shader = paint.refShader(); | |
| if (!shader) { | |
| shader = sk_make_sp<PaintColorShader>(paint.getColor4f().isOpaque()); | |
| } else if (paint.getAlphaf() < 1.0f) { | |
| shader = sk_make_sp<SkColorFilterShader>(std::move(shader), | |
| paint.getAlphaf(), | |
| sk_make_sp<NoopColorFilter>()); | |
| } | |
| // Add dither to the end of the shader pipeline if requested and needed. | |
| if (paint.isDither() && !as_SB(shader)->isConstant()) { | |
| shader = sk_make_sp<DitherShader>(std::move(shader)); | |
| } | |
| // Add the blender. | |
| sk_sp<SkBlender> blender = paint.refBlender(); | |
| if (!blender) { | |
| blender = SkBlender::Mode(SkBlendMode::kSrcOver); | |
| } | |
| // The most common blend mode is SrcOver, and it can be strength-reduced | |
| // _greatly_ to Src mode when the shader is opaque. | |
| // | |
| // In general all the information we use to make decisions here need to | |
| // be reflected in Params and Key to make program caching sound, and it | |
| // might appear that shader->isOpaque() is a property of the shader's | |
| // uniforms than its fundamental program structure and so unsafe to use. | |
| // | |
| // Opacity is such a powerful property that SkShaderBase::program() | |
| // forces opacity for any shader subclass that claims isOpaque(), so | |
| // the opaque bit is strongly guaranteed to be part of the program and | |
| // not just a property of the uniforms. The shader program hash includes | |
| // this information, making it safe to use anywhere in the blitter codegen. | |
| if (as_BB(blender)->asBlendMode() == SkBlendMode::kSrcOver && shader->isOpaque()) { | |
| blender = SkBlender::Mode(SkBlendMode::kSrc); | |
| } | |
| SkColor4f paintColor = paint.getColor4f(); | |
| SkColorSpaceXformSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, | |
| device.colorSpace(), kUnpremul_SkAlphaType} | |
| .apply(paintColor.vec()); | |
| return { | |
| std::move(shader), | |
| std::move(clip), | |
| std::move(blender), | |
| { device.colorType(), device.alphaType(), device.refColorSpace() }, | |
| Coverage::Full, // Placeholder... withCoverage() will change as needed. | |
| paintColor, | |
| matrices, | |
| }; | |
| } | |
| skvm::Color SkVMBlitter::DstColor(skvm::Builder* p, const Params& params) { | |
| skvm::PixelFormat dstFormat = skvm::SkColorType_to_PixelFormat(params.dst.colorType()); | |
| skvm::Ptr dst_ptr = p->varying(SkColorTypeBytesPerPixel(params.dst.colorType())); | |
| return p->load(dstFormat, dst_ptr); | |
| } | |
| void SkVMBlitter::BuildProgram(skvm::Builder* p, const Params& params, | |
| skvm::Uniforms* uniforms, SkArenaAlloc* alloc) { | |
| // First two arguments are always uniforms and the destination buffer. | |
| uniforms->base = p->uniform(); | |
| skvm::Ptr dst_ptr = p->varying(SkColorTypeBytesPerPixel(params.dst.colorType())); | |
| // A SpriteShader (in this file) may next use one argument as its varying source. | |
| // Subsequent arguments depend on params.coverage: | |
| // - Full: (no more arguments) | |
| // - Mask3D: mul varying, add varying, 8-bit coverage varying | |
| // - MaskA8: 8-bit coverage varying | |
| // - MaskLCD16: 565 coverage varying | |
| // - UniformF: float coverage uniform | |
| skvm::Coord device = device_coord(p, uniforms); | |
| skvm::Color paint = p->uniformColor(params.paint, uniforms); | |
| // See note about arguments above: a SpriteShader will call p->arg() once during program(). | |
| skvm::Color src = as_SB(params.shader)->program(p, device, /*local=*/device, paint, | |
| params.matrices, /*localM=*/nullptr, | |
| params.dst, uniforms, alloc); | |
| SkASSERT(src); | |
| if (params.coverage == Coverage::Mask3D) { | |
| skvm::F32 M = from_unorm(8, p->load8(p->varying<uint8_t>())), | |
| A = from_unorm(8, p->load8(p->varying<uint8_t>())); | |
| src.r = min(src.r * M + A, src.a); | |
| src.g = min(src.g * M + A, src.a); | |
| src.b = min(src.b * M + A, src.a); | |
| } | |
| // If we can determine this we can skip a fair bit of clamping! | |
| bool src_in_gamut = false; | |
| // Normalized premul formats can surprisingly represent some out-of-gamut | |
| // values (e.g. r=0xff, a=0xee fits in unorm8 but r = 1.07), but most code | |
| // working with normalized premul colors is not prepared to handle r,g,b > a. | |
| // So we clamp the shader to gamut here before blending and coverage. | |
| // | |
| // In addition, GL clamps all its color channels to limits of the format just | |
| // before the blend step (~here). To match that auto-clamp, we clamp alpha to | |
| // [0,1] too, just in case someone gave us an out of range alpha. | |
| if (!src_in_gamut | |
| && params.dst.alphaType() == kPremul_SkAlphaType | |
| && SkColorTypeIsNormalized(params.dst.colorType())) { | |
| src.a = clamp(src.a, 0.0f, 1.0f); | |
| src.r = clamp(src.r, 0.0f, src.a); | |
| src.g = clamp(src.g, 0.0f, src.a); | |
| src.b = clamp(src.b, 0.0f, src.a); | |
| src_in_gamut = true; | |
| } | |
| // Load the destination color. | |
| skvm::PixelFormat dstFormat = skvm::SkColorType_to_PixelFormat(params.dst.colorType()); | |
| skvm::Color dst = p->load(dstFormat, dst_ptr); | |
| if (params.dst.isOpaque()) { | |
| // When a destination is known opaque, we may assume it both starts and stays fully | |
| // opaque, ignoring any math that disagrees. This sometimes trims a little work. | |
| dst.a = p->splat(1.0f); | |
| } else if (params.dst.alphaType() == kUnpremul_SkAlphaType) { | |
| // All our blending works in terms of premul. | |
| dst = premul(dst); | |
| } | |
| // Load coverage. | |
| skvm::Color cov; | |
| switch (params.coverage) { | |
| case Coverage::Full: | |
| cov.r = cov.g = cov.b = cov.a = p->splat(1.0f); | |
| break; | |
| case Coverage::UniformF: | |
| cov.r = cov.g = cov.b = cov.a = p->uniformF(p->uniform(), 0); | |
| break; | |
| case Coverage::Mask3D: | |
| case Coverage::MaskA8: | |
| cov.r = cov.g = cov.b = cov.a = from_unorm(8, p->load8(p->varying<uint8_t>())); | |
| break; | |
| case Coverage::MaskLCD16: { | |
| skvm::PixelFormat fmt = skvm::SkColorType_to_PixelFormat(kRGB_565_SkColorType); | |
| cov = p->load(fmt, p->varying<uint16_t>()); | |
| cov.a = select(src.a < dst.a, min(cov.r, min(cov.g, cov.b)), | |
| max(cov.r, max(cov.g, cov.b))); | |
| } break; | |
| } | |
| if (params.clip) { | |
| skvm::Color clip = as_SB(params.clip)->program(p, device, /*local=*/device, paint, | |
| params.matrices, /*localM=*/nullptr, | |
| params.dst, uniforms, alloc); | |
| SkAssertResult(clip); | |
| cov.r *= clip.a; // We use the alpha channel of clip for all four. | |
| cov.g *= clip.a; | |
| cov.b *= clip.a; | |
| cov.a *= clip.a; | |
| } | |
| const SkBlenderBase* blender = as_BB(params.blender); | |
| const auto as_blendmode = blender->asBlendMode(); | |
| // The math for some blend modes lets us fold coverage into src before the blend, which is | |
| // simpler than the canonical post-blend lerp(). | |
| bool applyPostBlendCoverage = true; | |
| if (as_blendmode && | |
| SkBlendMode_ShouldPreScaleCoverage(as_blendmode.value(), | |
| params.coverage == Coverage::MaskLCD16)) { | |
| applyPostBlendCoverage = false; | |
| src.r *= cov.r; | |
| src.g *= cov.g; | |
| src.b *= cov.b; | |
| src.a *= cov.a; | |
| } | |
| // Apply our blend function to the computed color. | |
| src = blender->program(p, src, dst, params.dst, uniforms, alloc); | |
| if (applyPostBlendCoverage) { | |
| src.r = lerp(dst.r, src.r, cov.r); | |
| src.g = lerp(dst.g, src.g, cov.g); | |
| src.b = lerp(dst.b, src.b, cov.b); | |
| src.a = lerp(dst.a, src.a, cov.a); | |
| } | |
| if (params.dst.isOpaque()) { | |
| // (See the note above when loading the destination color.) | |
| src.a = p->splat(1.0f); | |
| } else if (params.dst.alphaType() == kUnpremul_SkAlphaType) { | |
| src = unpremul(src); | |
| } | |
| // Clamp to fit destination color format if needed. | |
| if (as_blendmode && src_in_gamut) { | |
| // An in-gamut src blended with an in-gamut dst should stay in gamut. | |
| // Being in-gamut implies all channels are in [0,1], so no need to clamp. | |
| // We allow one ulp error above 1.0f, and about that much (~1.2e-7) below 0. | |
| skvm::F32 lo = pun_to_F32(p->splat(0xb400'0000)), | |
| hi = pun_to_F32(p->splat(0x3f80'0001)); | |
| assert_true(src.r == clamp(src.r, lo, hi), src.r); | |
| assert_true(src.g == clamp(src.g, lo, hi), src.g); | |
| assert_true(src.b == clamp(src.b, lo, hi), src.b); | |
| assert_true(src.a == clamp(src.a, lo, hi), src.a); | |
| } else if (SkColorTypeIsNormalized(params.dst.colorType())) { | |
| src = clamp01(src); | |
| } | |
| // Write it out! | |
| store(dstFormat, dst_ptr, src); | |
| } | |
| // If BuildProgram() can't build this program, CacheKey() sets *ok to false. | |
| SkVMBlitter::Key SkVMBlitter::CacheKey( | |
| const Params& params, skvm::Uniforms* uniforms, SkArenaAlloc* alloc, bool* ok) { | |
| // Take care to match buildProgram()'s reuse of the paint color uniforms. | |
| skvm::Uniform r = uniforms->pushF(params.paint.fR), | |
| g = uniforms->pushF(params.paint.fG), | |
| b = uniforms->pushF(params.paint.fB), | |
| a = uniforms->pushF(params.paint.fA); | |
| auto hash_shader = [&](skvm::Builder& p, const sk_sp<SkShader>& shader, | |
| skvm::Color* outColor) { | |
| const SkShaderBase* sb = as_SB(shader); | |
| skvm::Coord device = device_coord(&p, uniforms); | |
| skvm::Color paint = { | |
| p.uniformF(r), | |
| p.uniformF(g), | |
| p.uniformF(b), | |
| p.uniformF(a), | |
| }; | |
| uint64_t hash = 0; | |
| *outColor = sb->program(&p, device, /*local=*/device, paint, params.matrices, | |
| /*localM=*/nullptr, params.dst, uniforms, alloc); | |
| if (*outColor) { | |
| hash = p.hash(); | |
| // p.hash() folds in all instructions to produce r,g,b,a but does not know | |
| // precisely which value we'll treat as which channel. Imagine the shader | |
| // called std::swap(*r,*b)... it draws differently, but p.hash() is unchanged. | |
| // We'll fold the hash of their IDs in order to disambiguate. | |
| const skvm::Val outputs[] = { | |
| outColor->r.id, | |
| outColor->g.id, | |
| outColor->b.id, | |
| outColor->a.id | |
| }; | |
| hash ^= SkOpts::hash(outputs, sizeof(outputs)); | |
| } else { | |
| *ok = false; | |
| } | |
| return hash; | |
| }; | |
| // Use this builder for shader, clip and blender, so that color objects that pass | |
| // from one to the other all 'make sense' -- i.e. have the same builder and/or have | |
| // meaningful values for the hash. | |
| // | |
| // Question: better if we just pass in mock uniform colors, so we don't need to | |
| // explicitly use the output color from one stage as input to another? | |
| // | |
| skvm::Builder p; | |
| // Calculate a hash for the color shader. | |
| SkASSERT(params.shader); | |
| skvm::Color src; | |
| uint64_t shaderHash = hash_shader(p, params.shader, &src); | |
| // Calculate a hash for the clip shader, if one exists. | |
| uint64_t clipHash = 0; | |
| if (params.clip) { | |
| skvm::Color cov; | |
| clipHash = hash_shader(p, params.clip, &cov); | |
| if (clipHash == 0) { | |
| clipHash = 1; | |
| } | |
| } | |
| // Calculate a hash for the blender. | |
| uint64_t blendHash = 0; | |
| if (auto bm = as_BB(params.blender)->asBlendMode()) { | |
| blendHash = static_cast<uint8_t>(bm.value()); | |
| } else if (*ok) { | |
| const SkBlenderBase* blender = as_BB(params.blender); | |
| skvm::Color dst = DstColor(&p, params); | |
| skvm::Color outColor = blender->program(&p, src, dst, params.dst, uniforms, alloc); | |
| if (outColor) { | |
| blendHash = p.hash(); | |
| // Like in `hash_shader` above, we must fold the color component IDs into our hash. | |
| const skvm::Val outputs[] = { | |
| outColor.r.id, | |
| outColor.g.id, | |
| outColor.b.id, | |
| outColor.a.id | |
| }; | |
| blendHash ^= SkOpts::hash(outputs, sizeof(outputs)); | |
| } else { | |
| *ok = false; | |
| } | |
| if (blendHash == 0) { | |
| blendHash = 1; | |
| } | |
| } | |
| return { | |
| shaderHash, | |
| clipHash, | |
| blendHash, | |
| params.dst.colorSpace() ? params.dst.colorSpace()->hash() : 0, | |
| SkToU8(params.dst.colorType()), | |
| SkToU8(params.dst.alphaType()), | |
| SkToU8(params.coverage), | |
| }; | |
| } | |
| SkVMBlitter::SkVMBlitter(const SkPixmap& device, | |
| const SkPaint& paint, | |
| const SkPixmap* sprite, | |
| SkIPoint spriteOffset, | |
| const SkMatrixProvider& matrices, | |
| sk_sp<SkShader> clip, | |
| bool* ok) | |
| : fDevice(device), fSprite(sprite ? *sprite : SkPixmap{}) | |
| , fSpriteOffset(spriteOffset) | |
| , fUniforms(skvm::UPtr{{0}}, kBlitterUniformsCount) | |
| , fParams(EffectiveParams(device, sprite, paint, matrices, std::move(clip))) | |
| , fKey(CacheKey(fParams, &fUniforms, &fAlloc, ok)) {} | |
| SkVMBlitter::~SkVMBlitter() { | |
| if (SkLRUCache<Key, skvm::Program>* cache = TryAcquireProgramCache()) { | |
| auto cache_program = [&](skvm::Program&& program, Coverage coverage) { | |
| if (!program.empty()) { | |
| cache->insert_or_update(fKey.withCoverage(coverage), std::move(program)); | |
| } | |
| }; | |
| cache_program(std::move(fBlitH), Coverage::Full); | |
| cache_program(std::move(fBlitAntiH), Coverage::UniformF); | |
| cache_program(std::move(fBlitMaskA8), Coverage::MaskA8); | |
| cache_program(std::move(fBlitMask3D), Coverage::Mask3D); | |
| cache_program(std::move(fBlitMaskLCD16), Coverage::MaskLCD16); | |
| ReleaseProgramCache(); | |
| } | |
| } | |
| SkLRUCache<SkVMBlitter::Key, skvm::Program>* SkVMBlitter::TryAcquireProgramCache() { | |
| #if 1 && defined(SKVM_JIT) | |
| thread_local static SkLRUCache<Key, skvm::Program> cache{64}; | |
| return &cache; | |
| #else | |
| // iOS in particular does not support thread_local until iOS 9.0. | |
| // On the other hand, we'll never be able to JIT there anyway. | |
| // It's probably fine to not cache any interpreted programs, anywhere. | |
| return nullptr; | |
| #endif | |
| } | |
| SkString SkVMBlitter::DebugName(const Key& key) { | |
| return SkStringPrintf("Shader-%" PRIx64 "_Clip-%" PRIx64 "_Blender-%" PRIx64 | |
| "_CS-%" PRIx64 "_CT-%d_AT-%d_Cov-%d", | |
| key.shader, | |
| key.clip, | |
| key.blender, | |
| key.colorSpace, | |
| key.colorType, | |
| key.alphaType, | |
| key.coverage); | |
| } | |
| void SkVMBlitter::ReleaseProgramCache() {} | |
| skvm::Program SkVMBlitter::buildProgram(Coverage coverage) { | |
| Key key = fKey.withCoverage(coverage); | |
| { | |
| skvm::Program p; | |
| if (SkLRUCache<Key, skvm::Program>* cache = TryAcquireProgramCache()) { | |
| if (skvm::Program* found = cache->find(key)) { | |
| p = std::move(*found); | |
| } | |
| ReleaseProgramCache(); | |
| } | |
| if (!p.empty()) { | |
| return p; | |
| } | |
| } | |
| // We don't really _need_ to rebuild fUniforms here. | |
| // It's just more natural to have effects unconditionally emit them, | |
| // and more natural to rebuild fUniforms than to emit them into a temporary buffer. | |
| // fUniforms should reuse the exact same memory, so this is very cheap. | |
| SkDEBUGCODE(size_t prev = fUniforms.buf.size();) | |
| fUniforms.buf.resize(kBlitterUniformsCount); | |
| skvm::Builder builder; | |
| BuildProgram(&builder, fParams.withCoverage(coverage), &fUniforms, &fAlloc); | |
| SkASSERTF(fUniforms.buf.size() == prev, | |
| "%zu, prev was %zu", fUniforms.buf.size(), prev); | |
| skvm::Program program = builder.done(DebugName(key).c_str()); | |
| if (false) { | |
| static std::atomic<int> missed{0}, | |
| total{0}; | |
| if (!program.hasJIT()) { | |
| SkDebugf("\ncouldn't JIT %s\n", DebugName(key).c_str()); | |
| builder.dump(); | |
| program.dump(); | |
| missed++; | |
| } | |
| if (0 == total++) { | |
| atexit([]{ SkDebugf("SkVMBlitter compiled %d programs, %d without JIT.\n", | |
| total.load(), missed.load()); }); | |
| } | |
| } | |
| return program; | |
| } | |
| void SkVMBlitter::updateUniforms(int right, int y) { | |
| BlitterUniforms uniforms{right, y}; | |
| memcpy(fUniforms.buf.data(), &uniforms, sizeof(BlitterUniforms)); | |
| } | |
| const void* SkVMBlitter::isSprite(int x, int y) const { | |
| if (fSprite.colorType() != kUnknown_SkColorType) { | |
| return fSprite.addr(x - fSpriteOffset.x(), | |
| y - fSpriteOffset.y()); | |
| } | |
| return nullptr; | |
| } | |
| void SkVMBlitter::blitH(int x, int y, int w) { | |
| if (fBlitH.empty()) { | |
| fBlitH = this->buildProgram(Coverage::Full); | |
| } | |
| this->updateUniforms(x+w, y); | |
| if (const void* sprite = this->isSprite(x,y)) { | |
| fBlitH.eval(w, fUniforms.buf.data(), fDevice.addr(x,y), sprite); | |
| } else { | |
| fBlitH.eval(w, fUniforms.buf.data(), fDevice.addr(x,y)); | |
| } | |
| } | |
| void SkVMBlitter::blitAntiH(int x, int y, const SkAlpha cov[], const int16_t runs[]) { | |
| if (fBlitAntiH.empty()) { | |
| fBlitAntiH = this->buildProgram(Coverage::UniformF); | |
| } | |
| for (int16_t run = *runs; run > 0; run = *runs) { | |
| this->updateUniforms(x+run, y); | |
| const float covF = *cov * (1/255.0f); | |
| if (const void* sprite = this->isSprite(x,y)) { | |
| fBlitAntiH.eval(run, fUniforms.buf.data(), fDevice.addr(x,y), sprite, &covF); | |
| } else { | |
| fBlitAntiH.eval(run, fUniforms.buf.data(), fDevice.addr(x,y), &covF); | |
| } | |
| x += run; | |
| runs += run; | |
| cov += run; | |
| } | |
| } | |
| void SkVMBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { | |
| if (mask.fFormat == SkMask::kBW_Format) { | |
| return SkBlitter::blitMask(mask, clip); | |
| } | |
| const skvm::Program* program = nullptr; | |
| switch (mask.fFormat) { | |
| default: SkUNREACHABLE; // ARGB and SDF masks shouldn't make it here. | |
| case SkMask::k3D_Format: | |
| if (fBlitMask3D.empty()) { | |
| fBlitMask3D = this->buildProgram(Coverage::Mask3D); | |
| } | |
| program = &fBlitMask3D; | |
| break; | |
| case SkMask::kA8_Format: | |
| if (fBlitMaskA8.empty()) { | |
| fBlitMaskA8 = this->buildProgram(Coverage::MaskA8); | |
| } | |
| program = &fBlitMaskA8; | |
| break; | |
| case SkMask::kLCD16_Format: | |
| if (fBlitMaskLCD16.empty()) { | |
| fBlitMaskLCD16 = this->buildProgram(Coverage::MaskLCD16); | |
| } | |
| program = &fBlitMaskLCD16; | |
| break; | |
| } | |
| SkASSERT(program); | |
| if (program) { | |
| for (int y = clip.top(); y < clip.bottom(); y++) { | |
| int x = clip.left(), | |
| w = clip.width(); | |
| void* dptr = fDevice.writable_addr(x,y); | |
| auto mptr = (const uint8_t*)mask.getAddr(x,y); | |
| this->updateUniforms(x+w,y); | |
| if (program == &fBlitMask3D) { | |
| size_t plane = mask.computeImageSize(); | |
| if (const void* sprite = this->isSprite(x,y)) { | |
| program->eval(w, fUniforms.buf.data(), dptr, sprite, mptr + 1*plane | |
| , mptr + 2*plane | |
| , mptr + 0*plane); | |
| } else { | |
| program->eval(w, fUniforms.buf.data(), dptr, mptr + 1*plane | |
| , mptr + 2*plane | |
| , mptr + 0*plane); | |
| } | |
| } else { | |
| if (const void* sprite = this->isSprite(x,y)) { | |
| program->eval(w, fUniforms.buf.data(), dptr, sprite, mptr); | |
| } else { | |
| program->eval(w, fUniforms.buf.data(), dptr, mptr); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| SkVMBlitter* SkVMBlitter::Make(const SkPixmap& device, | |
| const SkPaint& paint, | |
| const SkMatrixProvider& matrices, | |
| SkArenaAlloc* alloc, | |
| sk_sp<SkShader> clip) { | |
| bool ok = true; | |
| SkVMBlitter* blitter = alloc->make<SkVMBlitter>( | |
| device, paint, /*sprite=*/nullptr, SkIPoint{0,0}, matrices, std::move(clip), &ok); | |
| return ok ? blitter : nullptr; | |
| } | |
| SkVMBlitter* SkVMBlitter::Make(const SkPixmap& device, | |
| const SkPaint& paint, | |
| const SkPixmap& sprite, | |
| int left, int top, | |
| SkArenaAlloc* alloc, | |
| sk_sp<SkShader> clip) { | |
| if (paint.getMaskFilter()) { | |
| // TODO: SkVM support for mask filters? definitely possible! | |
| return nullptr; | |
| } | |
| bool ok = true; | |
| auto blitter = alloc->make<SkVMBlitter>( | |
| device, paint, &sprite, SkIPoint{left,top}, | |
| SkSimpleMatrixProvider{SkMatrix{}}, std::move(clip), &ok); | |
| return ok ? blitter : nullptr; | |
| } |