Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit cf3594b

Browse files
reed-at-googleSkia Commit-Bot
authored andcommitted
Finish implementing blendmodes for skvm
Lifted directly from SkRasterPipeline_opts.h, transcribing into skvm. Change-Id: I777b5d86f11e571b8d7faff38a6e5b0e3c9cc6a3 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/278376 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Mike Klein <mtklein@google.com>
1 parent e41fa2d commit cf3594b

File tree

4 files changed

+294
-199
lines changed

4 files changed

+294
-199
lines changed

src/core/SkVM.cpp

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,66 @@ bool gSkVMJITViaDylib{false};
4747
#include <sys/mman.h> // mmap, mprotect
4848
#endif
4949

50+
// We're basing our implementation of non-separable blend modes on
51+
// https://www.w3.org/TR/compositing-1/#blendingnonseparable.
52+
// and
53+
// https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf
54+
// They're equivalent, but ES' math has been better simplified.
55+
//
56+
// Anything extra we add beyond that is to make the math work with premul inputs.
57+
58+
static skvm::F32 saturation(skvm::Builder* p, skvm::F32 r, skvm::F32 g, skvm::F32 b) {
59+
return p->sub(p->max(r, p->max(g, b)), p->min(r, p->min(g, b)));
60+
}
61+
62+
static skvm::F32 luminance(skvm::Builder* p, skvm::F32 r, skvm::F32 g, skvm::F32 b) {
63+
return p->mad(r, p->splat(0.30f),
64+
p->mad(g, p->splat(0.59f),
65+
p->mul(b, p->splat(0.11f))));
66+
}
67+
68+
static void set_sat(skvm::Builder* p, skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32 s) {
69+
auto mn = p->min(*r, p->min(*g, *b)),
70+
mx = p->max(*r, p->max(*g, *b)),
71+
sat = p->sub(mx, mn);
72+
73+
// Map min channel to 0, max channel to s, and scale the middle proportionally.
74+
auto scale = [&](auto c) {
75+
auto zero = p->splat(0.0f);
76+
return p->select(p->eq(sat, zero), zero, p->div(p->mul(p->sub(c, mn), s), sat));
77+
};
78+
*r = scale(*r);
79+
*g = scale(*g);
80+
*b = scale(*b);
81+
}
82+
83+
static void set_lum(skvm::Builder* p, skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32 lu) {
84+
auto diff = p->sub(lu, luminance(p, *r, *g, *b));
85+
*r = p->add(*r, diff);
86+
*g = p->add(*g, diff);
87+
*b = p->add(*b, diff);
88+
}
89+
90+
static void clip_color(skvm::Builder* p, skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32 a) {
91+
auto mn = p->min(*r, p->min(*g, *b)),
92+
mx = p->max(*r, p->max(*g, *b)),
93+
lu = luminance(p, *r, *g, *b);
94+
95+
auto clip = [&](auto c) {
96+
c = p->select(p->gte(mn, p->splat(0.0f)),
97+
c,
98+
p->add(lu, p->div(p->mul(p->sub(c, lu), lu), p->sub(lu, mn))));
99+
c = p->select(p->gt (mx, a),
100+
p->add(lu, p->div(p->mul(p->sub(c, lu), p->sub(a, lu)), p->sub(mx, lu))),
101+
c);
102+
// Sometimes without this we may dip just a little negative.
103+
return p->max(c, p->splat(0.0f));
104+
};
105+
*r = clip(*r);
106+
*g = clip(*g);
107+
*b = clip(*b);
108+
}
109+
50110
namespace skvm {
51111

52112
struct Program::Impl {
@@ -1090,6 +1150,239 @@ namespace skvm {
10901150
};
10911151
}
10921152

1153+
Color Builder::blend(SkBlendMode mode, Color src, Color dst) {
1154+
auto mma = [this](skvm::F32 x, skvm::F32 y, skvm::F32 z, skvm::F32 w) {
1155+
return mad(x,y, mul(z,w));
1156+
};
1157+
1158+
auto inv = [this](skvm::F32 x) { return sub(splat(1.0f), x); };
1159+
1160+
auto two = [this](skvm::F32 x) { return add(x, x); };
1161+
1162+
auto apply_rgba = [&](auto fn) {
1163+
return Color {
1164+
fn(src.r, dst.r),
1165+
fn(src.g, dst.g),
1166+
fn(src.b, dst.b),
1167+
fn(src.a, dst.a),
1168+
};
1169+
};
1170+
1171+
auto apply_rgb_srcover_a = [&](auto fn) {
1172+
return Color {
1173+
fn(src.r, dst.r),
1174+
fn(src.g, dst.g),
1175+
fn(src.b, dst.b),
1176+
mad(dst.a, inv(src.a), src.a), // srcover for alpha
1177+
};
1178+
};
1179+
1180+
auto non_sep = [&](auto R, auto G, auto B) {
1181+
return Color{
1182+
add(mma(src.r, inv(dst.a), dst.r, inv(src.a)), R),
1183+
add(mma(src.g, inv(dst.a), dst.g, inv(src.a)), G),
1184+
add(mma(src.b, inv(dst.a), dst.b, inv(src.a)), B),
1185+
mad(dst.a, inv(src.a), src.a), // srcover
1186+
};
1187+
};
1188+
1189+
switch (mode) {
1190+
default: SkASSERT(false); /*but also, for safety, fallthrough*/
1191+
1192+
case SkBlendMode::kClear: return {
1193+
splat(0.0f),
1194+
splat(0.0f),
1195+
splat(0.0f),
1196+
splat(0.0f),
1197+
};
1198+
1199+
case SkBlendMode::kSrc: return src;
1200+
case SkBlendMode::kDst: return dst;
1201+
1202+
case SkBlendMode::kDstOver: std::swap(src, dst); // fall-through
1203+
case SkBlendMode::kSrcOver:
1204+
return apply_rgba([&](auto s, auto d) {
1205+
return mad(d, inv(src.a), s);
1206+
});
1207+
1208+
case SkBlendMode::kDstIn: std::swap(src, dst); // fall-through
1209+
case SkBlendMode::kSrcIn:
1210+
return apply_rgba([&](auto s, auto d) {
1211+
return mul(s, dst.a);
1212+
});
1213+
1214+
case SkBlendMode::kDstOut: std::swap(src, dst); // fall-through
1215+
case SkBlendMode::kSrcOut:
1216+
return apply_rgba([&](auto s, auto d) {
1217+
return mul(s, inv(dst.a));
1218+
});
1219+
1220+
case SkBlendMode::kDstATop: std::swap(src, dst); // fall-through
1221+
case SkBlendMode::kSrcATop:
1222+
return apply_rgba([&](auto s, auto d) {
1223+
return mma(s, dst.a, d, inv(src.a));
1224+
});
1225+
1226+
case SkBlendMode::kXor:
1227+
return apply_rgba([&](auto s, auto d) {
1228+
return mma(s, inv(dst.a), d, inv(src.a));
1229+
});
1230+
1231+
case SkBlendMode::kPlus:
1232+
return apply_rgba([&](auto s, auto d) {
1233+
return min(add(s, d), splat(1.0f));
1234+
});
1235+
1236+
case SkBlendMode::kModulate:
1237+
return apply_rgba([&](auto s, auto d) {
1238+
return mul(s, d);
1239+
});
1240+
1241+
case SkBlendMode::kScreen:
1242+
// (s+d)-(s*d) gave us trouble with our "r,g,b <= after blending" asserts.
1243+
// It's kind of plausible that s + (d - sd) keeps more precision?
1244+
return apply_rgba([&](auto s, auto d) {
1245+
return add(s, sub(d, mul(s, d)));
1246+
});
1247+
1248+
case SkBlendMode::kDarken:
1249+
return apply_rgb_srcover_a([&](auto s, auto d) {
1250+
return add(s, sub(d, max(mul(s, dst.a),
1251+
mul(d, src.a))));
1252+
});
1253+
1254+
case SkBlendMode::kLighten:
1255+
return apply_rgb_srcover_a([&](auto s, auto d) {
1256+
return add(s, sub(d, min(mul(s, dst.a),
1257+
mul(d, src.a))));
1258+
});
1259+
1260+
case SkBlendMode::kDifference:
1261+
return apply_rgb_srcover_a([&](auto s, auto d) {
1262+
return add(s, sub(d, two(min(mul(s, dst.a),
1263+
mul(d, src.a)))));
1264+
});
1265+
1266+
case SkBlendMode::kExclusion:
1267+
return apply_rgb_srcover_a([&](auto s, auto d) {
1268+
return add(s, sub(d, two(mul(s, d))));
1269+
});
1270+
1271+
case SkBlendMode::kColorBurn:
1272+
return apply_rgb_srcover_a([&](auto s, auto d) {
1273+
auto mn = min(dst.a,
1274+
div(mul(sub(dst.a, d), src.a), s)),
1275+
burn = mad(src.a, sub(dst.a, mn), mma(s, inv(dst.a), d, inv(src.a)));
1276+
return select(eq(d, dst.a), mad(s, inv(dst.a), d),
1277+
select(eq(s, splat(0.0f)), mul(d, inv(src.a)), burn));
1278+
});
1279+
1280+
case SkBlendMode::kColorDodge:
1281+
return apply_rgb_srcover_a([&](auto s, auto d) {
1282+
auto tmp = mad(src.a, min(dst.a,
1283+
div(mul(d, src.a), sub(src.a, s))),
1284+
mma(s, inv(dst.a), d, inv(src.a)));
1285+
return select(eq(d, splat(0.0f)), mul(s, inv(dst.a)),
1286+
select(eq(s, src.a), mad(d, inv(src.a), s), tmp));
1287+
});
1288+
1289+
case SkBlendMode::kHardLight:
1290+
return apply_rgb_srcover_a([&](auto s, auto d) {
1291+
return add(mma(s, inv(dst.a), d, inv(src.a)),
1292+
select(lte(two(s), src.a),
1293+
two(mul(s, d)),
1294+
sub(mul(src.a, dst.a), two(mul(sub(dst.a, d), sub(src.a, s))))));
1295+
});
1296+
1297+
case SkBlendMode::kOverlay:
1298+
return apply_rgb_srcover_a([&](auto s, auto d) {
1299+
return add(mma(s, inv(dst.a), d, inv(src.a)),
1300+
select(lte(two(d), dst.a),
1301+
two(mul(s, d)),
1302+
sub(mul(src.a, dst.a), two(mul(sub(dst.a, d), sub(src.a, s))))));
1303+
});
1304+
1305+
case SkBlendMode::kMultiply:
1306+
return apply_rgba([&](auto s, auto d) {
1307+
return add(mma(s, inv(dst.a), d, inv(src.a)), mul(s, d));
1308+
});
1309+
1310+
case SkBlendMode::kSoftLight:
1311+
return apply_rgb_srcover_a([&](auto s, auto d) {
1312+
auto m = select(gt(dst.a, splat(0.0f)), div(d, dst.a), splat(0.0f)),
1313+
s2 = two(s),
1314+
m4 = two(two(m));
1315+
1316+
// The logic forks three ways:
1317+
// 1. dark src?
1318+
// 2. light src, dark dst?
1319+
// 3. light src, light dst?
1320+
1321+
// Used in case 1
1322+
auto darkSrc = mul(d, mad(sub(s2, src.a), inv(m), src.a)),
1323+
// Used in case 2
1324+
darkDst = mad(mad(m4, m4, m4), sub(m, splat(1.0f)), mul(splat(7.0f), m)),
1325+
// Used in case 3.
1326+
liteDst = sub(sqrt(m), m),
1327+
// Used in 2 or 3?
1328+
liteSrc = mad(mul(dst.a, sub(s2, src.a)),
1329+
select(lte(two(two(d)), dst.a), darkDst, liteDst),
1330+
mul(d, src.a));
1331+
return mad(s, inv(dst.a), mad(d,
1332+
inv(src.a),
1333+
select(lte(s2, src.a), darkSrc, liteSrc)));
1334+
1335+
1336+
});
1337+
1338+
case SkBlendMode::kHue: {
1339+
skvm::F32 R = mul(src.r, src.a),
1340+
G = mul(src.g, src.a),
1341+
B = mul(src.b, src.a);
1342+
1343+
set_sat(this, &R, &G, &B, mul(saturation(this, dst.r, dst.g, dst.b), src.a));
1344+
set_lum(this, &R, &G, &B, mul( luminance(this, dst.r, dst.g, dst.b), src.a));
1345+
clip_color(this, &R, &G, &B, mul(src.a, dst.a));
1346+
1347+
return non_sep(R, G, B);
1348+
}
1349+
1350+
case SkBlendMode::kSaturation: {
1351+
skvm::F32 R = mul(dst.r, src.a),
1352+
G = mul(dst.g, src.a),
1353+
B = mul(dst.b, src.a);
1354+
1355+
set_sat(this, &R, &G, &B, mul(saturation(this, src.r, src.g, src.b), dst.a));
1356+
set_lum(this, &R, &G, &B, mul( luminance(this, dst.r, dst.g, dst.b), src.a));
1357+
clip_color(this, &R, &G, &B, mul(src.a, dst.a));
1358+
1359+
return non_sep(R, G, B);
1360+
}
1361+
1362+
case SkBlendMode::kColor: {
1363+
skvm::F32 R = mul(src.r, dst.a),
1364+
G = mul(src.g, dst.a),
1365+
B = mul(src.b, dst.a);
1366+
1367+
set_lum(this, &R, &G, &B, mul(luminance(this, dst.r, dst.g, dst.b), src.a));
1368+
clip_color(this, &R, &G, &B, mul(src.a, dst.a));
1369+
1370+
return non_sep(R, G, B);
1371+
}
1372+
1373+
case SkBlendMode::kLuminosity: {
1374+
skvm::F32 R = mul(dst.r, src.a),
1375+
G = mul(dst.g, src.a),
1376+
B = mul(dst.b, src.a);
1377+
1378+
set_lum(this, &R, &G, &B, mul(luminance(this, src.r, src.g, src.b), dst.a));
1379+
clip_color(this, &R, &G, &B, mul(src.a, dst.a));
1380+
1381+
return non_sep(R, G, B);
1382+
}
1383+
}
1384+
}
1385+
10931386
// ~~~~ Program::eval() and co. ~~~~ //
10941387

10951388
// Handy references for x86-64 instruction encoding:

src/core/SkVM.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,6 @@ namespace skvm {
690690
std::unique_ptr<Impl> fImpl;
691691
};
692692

693-
bool BlendModeSupported(SkBlendMode);
694-
695693
// TODO: control flow
696694
// TODO: 64-bit values?
697695
// TODO: SSE2/SSE4.1, AVX-512F, ARMv8.2 JITs?

0 commit comments

Comments
 (0)