blob: e8f4f0922b30c08aeffc1ef45b45d8aad0e4f8f8 [file] [log] [blame]
/*
* 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;
}