| /* |
| * 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/SkMacros.h" |
| #include "src/core/SkArenaAlloc.h" |
| #include "src/core/SkBlendModePriv.h" |
| #include "src/core/SkColorSpacePriv.h" |
| #include "src/core/SkColorSpaceXformSteps.h" |
| #include "src/core/SkCoreBlitters.h" |
| #include "src/core/SkLRUCache.h" |
| #include "src/core/SkVM.h" |
| |
| #if defined(STARBOARD) |
| #include "starboard/common/log.h" |
| #endif |
| |
| namespace { |
| |
| enum class Coverage { Full, UniformA8, MaskA8, MaskLCD16, Mask3D }; |
| |
| SK_BEGIN_REQUIRE_DENSE; |
| struct Key { |
| SkColorType colorType; |
| SkAlphaType alphaType; |
| Coverage coverage; |
| SkBlendMode blendMode; |
| SkShader* shader; |
| SkColorFilter* colorFilter; |
| |
| Key withCoverage(Coverage c) const { |
| Key k = *this; |
| k.coverage = c; |
| return k; |
| } |
| }; |
| SK_END_REQUIRE_DENSE; |
| |
| static bool operator==(const Key& x, const Key& y) { |
| return x.colorType == y.colorType |
| && x.alphaType == y.alphaType |
| && x.coverage == y.coverage |
| && x.blendMode == y.blendMode |
| && x.shader == y.shader |
| && x.colorFilter == y.colorFilter; |
| } |
| |
| static SkString debug_name(const Key& key) { |
| return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-Shader%d-CF%d", |
| key.colorType, |
| key.alphaType, |
| key.coverage, |
| key.blendMode, |
| SkToBool(key.shader), |
| SkToBool(key.colorFilter)); |
| } |
| |
| static bool debug_dump(const Key& key) { |
| #if 0 |
| SkDebugf("%s\n", debug_name(key).c_str()); |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| static SkLRUCache<Key, skvm::Program>* try_acquire_program_cache() { |
| #if defined(STARBOARD) |
| SB_NOTREACHED(); |
| return nullptr; |
| #elif defined(SK_BUILD_FOR_IOS) |
| // iOS doesn't support thread_local on versions less than 9.0. pthread |
| // based fallbacks must be used there. We could also use an SkSpinlock |
| // and tryAcquire()/release(), or... |
| return nullptr; // ... we could just not cache programs on those platforms. |
| #else |
| thread_local static auto* cache = new SkLRUCache<Key, skvm::Program>{8}; |
| return cache; |
| #endif |
| } |
| |
| static void release_program_cache() { } |
| |
| |
| struct Uniforms { |
| uint32_t paint_color; |
| uint8_t coverage; // Used when Coverage::UniformA8. |
| }; |
| |
| struct Builder : public skvm::Builder { |
| //using namespace skvm; |
| |
| struct Color { skvm::I32 r,g,b,a; }; |
| |
| |
| skvm::I32 inv(skvm::I32 x) { |
| return sub(splat(255), x); |
| } |
| |
| // TODO: provide this in skvm::Builder, with a custom NEON impl. |
| skvm::I32 div255(skvm::I32 v) { |
| // This should be a bit-perfect version of (v+127)/255, |
| // implemented as (v + ((v+128)>>8) + 128)>>8. |
| skvm::I32 v128 = add(v, splat(128)); |
| return shr(add(v128, shr(v128, 8)), 8); |
| } |
| |
| skvm::I32 mix(skvm::I32 x, skvm::I32 y, skvm::I32 t) { |
| return div255(add(mul(x, inv(t)), |
| mul(y, t ))); |
| } |
| |
| Color unpack_8888(skvm::I32 rgba) { |
| return { |
| extract(rgba, 0, splat(0xff)), |
| extract(rgba, 8, splat(0xff)), |
| extract(rgba, 16, splat(0xff)), |
| extract(rgba, 24, splat(0xff)), |
| }; |
| } |
| |
| skvm::I32 pack_8888(Color c) { |
| return pack(pack(c.r, c.g, 8), |
| pack(c.b, c.a, 8), 16); |
| } |
| |
| Color unpack_565(skvm::I32 bgr) { |
| // N.B. kRGB_565_SkColorType is named confusingly; |
| // blue is in the low bits and red the high. |
| skvm::I32 r = extract(bgr, 11, splat(0b011'111)), |
| g = extract(bgr, 5, splat(0b111'111)), |
| b = extract(bgr, 0, splat(0b011'111)); |
| return { |
| // Scale 565 up to 888. |
| bit_or(shl(r, 3), shr(r, 2)), |
| bit_or(shl(g, 2), shr(g, 4)), |
| bit_or(shl(b, 3), shr(b, 2)), |
| splat(0xff), |
| }; |
| } |
| |
| skvm::I32 pack_565(Color c) { |
| skvm::I32 r = div255(mul(c.r, splat(31))), |
| g = div255(mul(c.g, splat(63))), |
| b = div255(mul(c.b, splat(31))); |
| return pack(pack(b, g,5), r,11); |
| } |
| |
| // TODO: add native min/max ops to skvm::Builder |
| skvm::I32 min(skvm::I32 x, skvm::I32 y) { return select(lt(x,y), x,y); } |
| skvm::I32 max(skvm::I32 x, skvm::I32 y) { return select(gt(x,y), x,y); } |
| |
| static bool CanBuild(const Key& key) { |
| // These checks parallel the TODOs in Builder::Builder(). |
| if (key.shader) { return false; } |
| if (key.colorFilter) { return false; } |
| |
| switch (key.colorType) { |
| default: return false; |
| case kRGB_565_SkColorType: break; |
| case kRGBA_8888_SkColorType: break; |
| case kBGRA_8888_SkColorType: break; |
| } |
| |
| if (key.alphaType == kUnpremul_SkAlphaType) { return false; } |
| |
| switch (key.blendMode) { |
| default: return false; |
| case SkBlendMode::kSrc: break; |
| case SkBlendMode::kSrcOver: break; |
| } |
| |
| return true; |
| } |
| |
| explicit Builder(const Key& key) { |
| #define TODO SkUNREACHABLE |
| SkASSERT(CanBuild(key)); |
| skvm::Arg uniforms = uniform(), |
| dst_ptr = arg(SkColorTypeBytesPerPixel(key.colorType)); |
| // When coverage is MaskA8 or MaskLCD16 there will be one more mask varying, |
| // and when coverage is Mask3D there will be three more mask varyings. |
| |
| |
| // When there's no shader and no color filter, the source color is the paint color. |
| if (key.shader) { TODO; } |
| if (key.colorFilter) { TODO; } |
| Color src = unpack_8888(uniform32(uniforms, offsetof(Uniforms, paint_color))); |
| |
| // There are several orderings here of when we load dst and coverage |
| // and how coverage is applied, and to complicate things, LCD coverage |
| // needs to know dst.a. We're careful to assert it's loaded in time. |
| Color dst; |
| SkDEBUGCODE(bool dst_loaded = false;) |
| |
| // load_coverage() returns false when there's no need to apply coverage. |
| auto load_coverage = [&](Color* cov) { |
| switch (key.coverage) { |
| case Coverage::Full: return false; |
| |
| case Coverage::UniformA8: cov->r = cov->g = cov->b = cov->a = |
| uniform8(uniforms, offsetof(Uniforms, coverage)); |
| return true; |
| |
| case Coverage::MaskA8: cov->r = cov->g = cov->b = cov->a = |
| load8(varying<uint8_t>()); |
| return true; |
| |
| case Coverage::MaskLCD16: |
| SkASSERT(dst_loaded); |
| *cov = unpack_565(load16(varying<uint16_t>())); |
| cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g,cov->b)) |
| , max(cov->r, max(cov->g,cov->b))); |
| return true; |
| |
| case Coverage::Mask3D: TODO; |
| } |
| // GCC insists... |
| return false; |
| }; |
| |
| // The math for some blend modes lets us fold coverage into src before the blend, |
| // obviating the need for the lerp afterwards. This early-coverage strategy tends |
| // to be both faster and require fewer registers. |
| bool lerp_coverage_post_blend = true; |
| if (SkBlendMode_ShouldPreScaleCoverage(key.blendMode, |
| key.coverage == Coverage::MaskLCD16)) { |
| Color cov; |
| if (load_coverage(&cov)) { |
| src.r = div255(mul(src.r, cov.r)); |
| src.g = div255(mul(src.g, cov.g)); |
| src.b = div255(mul(src.b, cov.b)); |
| src.a = div255(mul(src.a, cov.a)); |
| } |
| lerp_coverage_post_blend = false; |
| } |
| |
| // Load up the destination color. |
| SkDEBUGCODE(dst_loaded = true;) |
| switch (key.colorType) { |
| default: TODO; |
| case kRGB_565_SkColorType: dst = unpack_565 (load16(dst_ptr)); break; |
| case kRGBA_8888_SkColorType: dst = unpack_8888(load32(dst_ptr)); break; |
| case kBGRA_8888_SkColorType: dst = unpack_8888(load32(dst_ptr)); |
| std::swap(dst.r, dst.b); |
| break; |
| } |
| |
| // When a destination is tagged opaque, we may assume it both starts and stays fully |
| // opaque, ignoring any math that disagrees. So anything involving force_opaque is |
| // optional, and sometimes helps cut a small amount of work in these programs. |
| const bool force_opaque = true && key.alphaType == kOpaque_SkAlphaType; |
| if (force_opaque) { dst.a = splat(0xff); } |
| |
| // We'd need to premul dst after loading and unpremul before storing. |
| if (key.alphaType == kUnpremul_SkAlphaType) { TODO; } |
| |
| // Blend src and dst. |
| switch (key.blendMode) { |
| default: TODO; |
| |
| case SkBlendMode::kSrc: break; |
| |
| case SkBlendMode::kSrcOver: { |
| src.r = add(src.r, div255(mul(dst.r, inv(src.a)))); |
| src.g = add(src.g, div255(mul(dst.g, inv(src.a)))); |
| src.b = add(src.b, div255(mul(dst.b, inv(src.a)))); |
| src.a = add(src.a, div255(mul(dst.a, inv(src.a)))); |
| } break; |
| } |
| |
| // Lerp with coverage post-blend if needed. |
| Color cov; |
| if (lerp_coverage_post_blend && load_coverage(&cov)) { |
| src.r = mix(dst.r, src.r, cov.r); |
| src.g = mix(dst.g, src.g, cov.g); |
| src.b = mix(dst.b, src.b, cov.b); |
| src.a = mix(dst.a, src.a, cov.a); |
| } |
| |
| if (force_opaque) { src.a = splat(0xff); } |
| |
| // Store back to the destination. |
| switch (key.colorType) { |
| default: SkUNREACHABLE; |
| |
| case kRGB_565_SkColorType: store16(dst_ptr, pack_565(src)); break; |
| |
| case kBGRA_8888_SkColorType: std::swap(src.r, src.b); // fallthrough |
| case kRGBA_8888_SkColorType: store32(dst_ptr, pack_8888(src)); break; |
| } |
| #undef TODO |
| } |
| }; |
| |
| class Blitter final : public SkBlitter { |
| public: |
| bool ok = false; |
| |
| Blitter(const SkPixmap& device, const SkPaint& paint) |
| : fDevice(device) |
| , fKey { |
| device.colorType(), |
| device.alphaType(), |
| Coverage::Full, |
| paint.getBlendMode(), |
| paint.getShader(), |
| paint.getColorFilter(), |
| } |
| { |
| SkColor4f color = paint.getColor4f(); |
| SkColorSpaceXformSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, |
| device.colorSpace(), kUnpremul_SkAlphaType}.apply(color.vec()); |
| |
| if (color.fitsInBytes() && Builder::CanBuild(fKey)) { |
| fUniforms.paint_color = color.premul().toBytes_RGBA(); |
| ok = true; |
| } |
| } |
| |
| ~Blitter() override { |
| if (SkLRUCache<Key, skvm::Program>* cache = try_acquire_program_cache()) { |
| auto cache_program = [&](skvm::Program&& program, Coverage coverage) { |
| if (!program.empty()) { |
| Key key = fKey.withCoverage(coverage); |
| if (skvm::Program* found = cache->find(key)) { |
| *found = std::move(program); |
| } else { |
| cache->insert(key, std::move(program)); |
| } |
| } |
| }; |
| cache_program(std::move(fBlitH), Coverage::Full); |
| cache_program(std::move(fBlitAntiH), Coverage::UniformA8); |
| cache_program(std::move(fBlitMaskA8), Coverage::MaskA8); |
| cache_program(std::move(fBlitMaskLCD16), Coverage::MaskLCD16); |
| |
| release_program_cache(); |
| } |
| } |
| |
| private: |
| SkPixmap fDevice; // TODO: can this be const&? |
| const Key fKey; |
| Uniforms fUniforms; |
| skvm::Program fBlitH, |
| fBlitAntiH, |
| fBlitMaskA8, |
| fBlitMaskLCD16; |
| |
| skvm::Program buildProgram(Coverage coverage) { |
| Key key = fKey.withCoverage(coverage); |
| { |
| skvm::Program p; |
| if (SkLRUCache<Key, skvm::Program>* cache = try_acquire_program_cache()) { |
| if (skvm::Program* found = cache->find(key)) { |
| p = std::move(*found); |
| } |
| release_program_cache(); |
| } |
| if (!p.empty()) { |
| return p; |
| } |
| } |
| #if 0 |
| static std::atomic<int> done{0}; |
| if (0 == done++) { |
| atexit([]{ SkDebugf("%d calls to done\n", done.load()); }); |
| } |
| #endif |
| Builder builder{key}; |
| skvm::Program program = builder.done(debug_name(key).c_str()); |
| if (!program.hasJIT() && debug_dump(key)) { |
| SkDebugf("\nfalling back to interpreter for blitter with this key.\n"); |
| builder.dump(); |
| program.dump(); |
| } |
| return program; |
| } |
| |
| void blitH(int x, int y, int w) override { |
| if (fBlitH.empty()) { |
| fBlitH = this->buildProgram(Coverage::Full); |
| } |
| fBlitH.eval(w, &fUniforms, fDevice.addr(x,y)); |
| } |
| |
| void blitAntiH(int x, int y, const SkAlpha cov[], const int16_t runs[]) override { |
| if (fBlitAntiH.empty()) { |
| fBlitAntiH = this->buildProgram(Coverage::UniformA8); |
| } |
| for (int16_t run = *runs; run > 0; run = *runs) { |
| fUniforms.coverage = *cov; |
| fBlitAntiH.eval(run, &fUniforms, fDevice.addr(x,y)); |
| |
| x += run; |
| runs += run; |
| cov += run; |
| } |
| } |
| |
| void blitMask(const SkMask& mask, const SkIRect& clip) override { |
| if (mask.fFormat == SkMask::kBW_Format) { |
| // TODO: native BW masks? |
| 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: // TODO: the mul and add 3D mask planes too |
| 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++) { |
| program->eval(clip.width(), |
| &fUniforms, |
| fDevice.addr(clip.left(), y), |
| mask.getAddr(clip.left(), y)); |
| } |
| } |
| } |
| }; |
| |
| } // namespace |
| |
| |
| SkBlitter* SkCreateSkVMBlitter(const SkPixmap& device, |
| const SkPaint& paint, |
| const SkMatrix& ctm, |
| SkArenaAlloc* alloc) { |
| auto blitter = alloc->make<Blitter>(device, paint); |
| return blitter->ok ? blitter |
| : nullptr; |
| } |