2pt conical stage for focal-point-outside case

A couple of annoyances here:

1) the prev vector_scale stage is not usable for masking, as NaN values can propagate through
   => switch to actual masking

2) for the outside case, we must select the min root when the gradient is flipped
   => split into two templated stages (_min, _max)

  (I'm not convinced that we need to flip the gradient for RP at all; we can investigate later)

Change-Id: I0283812d613a53124f2987d1aea1f26e4533655e
Reviewed-on: https://skia-review.googlesource.com/21162
Reviewed-by: Mike Klein <mtklein@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2017-06-29 11:03:45 -04:00 committed by Skia Commit-Bot
parent 762466e9fe
commit 9026fe13a7
6 changed files with 5087 additions and 4434 deletions

View File

@ -107,8 +107,10 @@ struct SkJumper_constants;
M(evenly_spaced_2_stop_gradient) \ M(evenly_spaced_2_stop_gradient) \
M(xy_to_unit_angle) \ M(xy_to_unit_angle) \
M(xy_to_radius) \ M(xy_to_radius) \
M(xy_to_2pt_conical_quadratic) M(xy_to_2pt_conical_linear) \ M(xy_to_2pt_conical_quadratic_min) \
M(vector_scale) \ M(xy_to_2pt_conical_quadratic_max) \
M(xy_to_2pt_conical_linear) \
M(mask_2pt_conical_degenerates) M(apply_vector_mask) \
M(byte_tables) M(byte_tables_rgb) \ M(byte_tables) M(byte_tables_rgb) \
M(rgb_to_hsl) \ M(rgb_to_hsl) \
M(hsl_to_rgb) M(hsl_to_rgb)

View File

@ -108,11 +108,11 @@ struct SkJumper_GradientCtx {
}; };
struct SkJumper_2PtConicalCtx { struct SkJumper_2PtConicalCtx {
float fMask[SkJumper_kMaxStride]; uint32_t fMask[SkJumper_kMaxStride];
float fCoeffA, float fCoeffA,
fInvCoeffA, fInvCoeffA,
fR0, fR0,
fDR; fDR;
}; };
#endif//SkJumper_DEFINED #endif//SkJumper_DEFINED

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1208,9 +1208,7 @@ STAGE(xy_to_radius) {
r = sqrt_(X2 + Y2); r = sqrt_(X2 + Y2);
} }
STAGE(xy_to_2pt_conical_quadratic) { SI F solve_2pt_conical_quadratic(const SkJumper_2PtConicalCtx* c, F x, F y, F (*select)(F, F)) {
auto* c = (const SkJumper_2PtConicalCtx*)ctx;
// At this point, (x, y) is mapped into a synthetic gradient space with // At this point, (x, y) is mapped into a synthetic gradient space with
// the start circle centerd on (0, 0), and the end circle centered on (1, 0) // the start circle centerd on (0, 0), and the end circle centered on (1, 0)
// (see the stage setup). // (see the stage setup).
@ -1230,54 +1228,62 @@ STAGE(xy_to_2pt_conical_quadratic) {
// //
// Since the start/end circle centers are the extremes of the [0, 1] interval // Since the start/end circle centers are the extremes of the [0, 1] interval
// on the X axis, the solution (x') is exactly the t we are looking for. // on the X axis, the solution (x') is exactly the t we are looking for.
//
// The setup code also ensures that we only use this stage when the discriminant
// is positive. So off we go...
const F coeffA = c->fCoeffA, const F coeffA = c->fCoeffA,
coeffB = -2 * (r + c->fDR * c->fR0), coeffB = -2 * (x + c->fDR*c->fR0),
coeffC = r * r + g * g - c->fR0 * c->fR0; coeffC = x*x + y*y - c->fR0*c->fR0;
const F disc = mad(coeffB, coeffB, -4 * coeffA * coeffC); const F disc = mad(coeffB, coeffB, -4 * coeffA * coeffC);
// SkASSERT(disc >= 0);
const F sqrt_disc = sqrt_(disc); const F sqrt_disc = sqrt_(disc);
const F invCoeffA = c->fInvCoeffA; const F invCoeffA = c->fInvCoeffA;
// We want the larger root, per spec: return select((-coeffB + sqrt_disc) * (invCoeffA * 0.5f),
// "For all values of ω where r(ω) > 0, starting with the value of ω nearest (-coeffB - sqrt_disc) * (invCoeffA * 0.5f));
// to positive infinity and ending with the value of ω nearest to negative }
// infinity, draw the circumference of the circle with radius r(ω) at position
// (x(ω), y(ω)), with the color at ω, but only painting on the parts of the STAGE(xy_to_2pt_conical_quadratic_max) {
// bitmap that have not yet been painted on by earlier circles in this step for r = solve_2pt_conical_quadratic(ctx, r, g, max);
// this rendering of the gradient." }
// (https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient)
r = max((-coeffB + sqrt_disc) * invCoeffA * .5f, STAGE(xy_to_2pt_conical_quadratic_min) {
(-coeffB - sqrt_disc) * invCoeffA * .5f); r = solve_2pt_conical_quadratic(ctx, r, g, min);
} }
STAGE(xy_to_2pt_conical_linear) { STAGE(xy_to_2pt_conical_linear) {
auto* c = (SkJumper_2PtConicalCtx*)ctx; auto* c = (const SkJumper_2PtConicalCtx*)ctx;
const F coeffB = -2 * (r + c->fDR * c->fR0), const F coeffB = -2 * (r + c->fDR*c->fR0),
coeffC = r * r + g * g - c->fR0 * c->fR0; coeffC = r*r + g*g - c->fR0*c->fR0;
r = -coeffC / coeffB; r = -coeffC / coeffB;
// Compute and save a mask for degenerate values.
g = 1.0f;
g = if_then_else(mad(r, c->fDR, c->fR0) < 0, F(0), g); // R(t) < 0
g = if_then_else(r != r , F(0), g); // NaN
unaligned_store(&c->fMask, g);
} }
STAGE(vector_scale) { STAGE(mask_2pt_conical_degenerates) {
const F scale = unaligned_load<F>((const float*)ctx); auto* c = (SkJumper_2PtConicalCtx*)ctx;
r = r * scale; // Compute and save a mask for degenerate values.
g = g * scale; U32 mask = 0xffffffff;
b = b * scale;
a = a * scale; // TODO: mtklein kindly volunteered to revisit this at some point.
#if defined(JUMPER)
// Vector comparisons set all bits, so we can use something like this.
mask = mask & (mad(r, c->fDR, c->fR0) >= 0); // R(t) >= 0
mask = mask & (r == r); // t != NaN
#else
// The portable version is more involved, 'cause we only get one bit back.
mask = mask & if_then_else(mad(r, c->fDR, c->fR0) >= 0, U32(0xffffffff), U32(0)); // R(t) >= 0
mask = mask & if_then_else(r == r, U32(0xffffffff), U32(0)); // t != NaN
#endif
unaligned_store(&c->fMask, mask);
}
STAGE(apply_vector_mask) {
const U32 mask = unaligned_load<U32>((const uint32_t*)ctx);
r = bit_cast<F>(bit_cast<U32>(r) & mask);
g = bit_cast<F>(bit_cast<U32>(g) & mask);
b = bit_cast<F>(bit_cast<U32>(b) & mask);
a = bit_cast<F>(bit_cast<U32>(a) & mask);
} }
STAGE(save_xy) { STAGE(save_xy) {

View File

@ -453,11 +453,6 @@ bool SkTwoPointConicalGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc,
return true; return true;
} }
if (dCenter + fRadius1 > fRadius2) {
// We only handle well behaved cases for now.
return false;
}
// To simplify the stage math, we transform the universe (translate/scale/rotate) // To simplify the stage math, we transform the universe (translate/scale/rotate)
// such that fCenter1 -> (0, 0) and fCenter2 -> (1, 0). // such that fCenter1 -> (0, 0) and fCenter2 -> (1, 0).
SkMatrix map_to_unit_vector; SkMatrix map_to_unit_vector;
@ -475,14 +470,37 @@ bool SkTwoPointConicalGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc,
ctx->fR0 = fRadius1 / dCenter; ctx->fR0 = fRadius1 / dCenter;
ctx->fDR = dRadius / dCenter; ctx->fDR = dRadius / dCenter;
// Is the solver guaranteed to not produce degenerates?
bool isWellBehaved = true;
if (SkScalarNearlyZero(coeffA)) { if (SkScalarNearlyZero(coeffA)) {
// The focal point is on the edge of the end circle. // The focal point is on the edge of the end circle.
p->append(SkRasterPipeline::xy_to_2pt_conical_linear, ctx); p->append(SkRasterPipeline::xy_to_2pt_conical_linear, ctx);
// To handle degenerate values (NaN, r < 0), the t stage sets up a scale/mask isWellBehaved = false;
// context, which we post-apply to force transparent black.
postPipeline->append(SkRasterPipeline::vector_scale, &ctx->fMask);
} else { } else {
p->append(SkRasterPipeline::xy_to_2pt_conical_quadratic, ctx); if (dCenter + fRadius1 > fRadius2) {
// The focal point is outside the end circle.
// We want the larger root, per spec:
// "For all values of ω where r(ω) > 0, starting with the value of ω nearest
// to positive infinity and ending with the value of ω nearest to negative
// infinity, draw the circumference of the circle with radius r(ω) at position
// (x(ω), y(ω)), with the color at ω, but only painting on the parts of the
// bitmap that have not yet been painted on by earlier circles in this step for
// this rendering of the gradient."
// (https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient)
p->append(fFlippedGrad ? SkRasterPipeline::xy_to_2pt_conical_quadratic_min
: SkRasterPipeline::xy_to_2pt_conical_quadratic_max, ctx);
isWellBehaved = false;
} else {
// The focal point is inside (well-behaved case).
p->append(SkRasterPipeline::xy_to_2pt_conical_quadratic_max, ctx);
}
}
if (!isWellBehaved) {
p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
} }
return true; return true;