Update 2pt conical gradient in raster pipeline
The updated algorithm matches our new GPU algorithm (https://skia.org/dev/design/conical) and it brings about 7%-26% speedup. In the next CL, I'll simplify the GPU code by reusing the CPU code in this CL. 7.20% faster in gradient_conical_clamp_hicolor 8.94% faster in gradient_conicalZero_clamp_hicolor 10.00% faster in gradient_conicalOut_clamp_hicolor 11.72% faster in gradient_conicalOutZero_clamp_hicolor 13.62% faster in gradient_conical_clamp_3color 16.52% faster in gradient_conicalZero_clamp_3color 17.48% faster in gradient_conical_clamp 17.70% faster in gradient_conical_clamp_shallow 20.60% faster in gradient_conicalOut_clamp_3color 20.98% faster in gradient_conicalOutZero_clamp_3color 21.79% faster in gradient_conicalZero_clamp 22.48% faster in gradient_conicalOut_clamp 26.13% faster in gradient_conicalOutZero_clamp Bug: skia: Change-Id: Ia159495e1c77658cb28e48c9edf84938464e501c Reviewed-on: https://skia-review.googlesource.com/90262 Commit-Queue: Yuqian Li <liyuqian@google.com> Reviewed-by: Mike Klein <mtklein@chromium.org>
This commit is contained in:
parent
fa3783f17d
commit
d208a88477
@ -36,6 +36,8 @@ struct SkJumper_Engine;
|
||||
* If you'd like to see how this works internally, you want to start digging around src/jumper.
|
||||
*/
|
||||
|
||||
// TODO (liyuqian): remove xy_to_2pt_conical_quadratic_first, xy_to_2pt_conical_quadratic_second,
|
||||
// xy_to_2pt_conical_linear, and mask_2pt_conical_degenerates_legacy once rebaselined.
|
||||
#define SK_RASTER_PIPELINE_STAGES(M) \
|
||||
M(callback) \
|
||||
M(move_src_dst) M(move_dst_src) \
|
||||
@ -76,6 +78,7 @@ struct SkJumper_Engine;
|
||||
M(lab_to_xyz) \
|
||||
M(mirror_x) M(repeat_x) \
|
||||
M(mirror_y) M(repeat_y) \
|
||||
M(negate_x) \
|
||||
M(bilinear_nx) M(bilinear_px) M(bilinear_ny) M(bilinear_py) \
|
||||
M(bicubic_n3x) M(bicubic_n1x) M(bicubic_p1x) M(bicubic_p3x) \
|
||||
M(bicubic_n3y) M(bicubic_n1y) M(bicubic_p1y) M(bicubic_p3y) \
|
||||
@ -89,6 +92,15 @@ struct SkJumper_Engine;
|
||||
M(xy_to_2pt_conical_quadratic_first) \
|
||||
M(xy_to_2pt_conical_quadratic_second) \
|
||||
M(xy_to_2pt_conical_linear) \
|
||||
M(mask_2pt_conical_degenerates_legacy) \
|
||||
M(xy_to_2pt_conical_strip) \
|
||||
M(xy_to_2pt_conical_focal_on_circle) \
|
||||
M(xy_to_2pt_conical_well_behaved) \
|
||||
M(xy_to_2pt_conical_smaller) \
|
||||
M(xy_to_2pt_conical_greater) \
|
||||
M(alter_2pt_conical_compensate_focal) \
|
||||
M(alter_2pt_conical_unswap) \
|
||||
M(mask_2pt_conical_nan) \
|
||||
M(mask_2pt_conical_degenerates) M(apply_vector_mask) \
|
||||
M(byte_tables) M(byte_tables_rgb) \
|
||||
M(rgb_to_hsl) M(hsl_to_rgb) \
|
||||
|
@ -252,10 +252,22 @@ extern "C" {
|
||||
LOWP(evenly_spaced_2_stop_gradient)
|
||||
LOWP(xy_to_unit_angle)
|
||||
LOWP(xy_to_radius)
|
||||
TODO(negate_x)
|
||||
// TODO (liyuqian): remove xy_to_2pt_conical_quadratic_first,
|
||||
// xy_to_2pt_conical_quadratic_second, mask_2pt_conical_degenerates_legacy,
|
||||
// and xy_to_2pt_conical_linear once rebaselined.
|
||||
TODO(xy_to_2pt_conical_quadratic_first)
|
||||
TODO(xy_to_2pt_conical_quadratic_second)
|
||||
TODO(xy_to_2pt_conical_linear)
|
||||
TODO(mask_2pt_conical_degenerates) TODO(apply_vector_mask)
|
||||
TODO(mask_2pt_conical_degenerates_legacy)
|
||||
TODO(xy_to_2pt_conical_strip)
|
||||
TODO(xy_to_2pt_conical_focal_on_circle)
|
||||
TODO(xy_to_2pt_conical_well_behaved)
|
||||
TODO(xy_to_2pt_conical_greater)
|
||||
TODO(xy_to_2pt_conical_smaller)
|
||||
TODO(alter_2pt_conical_compensate_focal)
|
||||
TODO(alter_2pt_conical_unswap)
|
||||
TODO(mask_2pt_conical_nan) TODO(mask_2pt_conical_degenerates) TODO(apply_vector_mask)
|
||||
TODO(byte_tables) TODO(byte_tables_rgb)
|
||||
NOPE(rgb_to_hsl) NOPE(hsl_to_rgb)
|
||||
NOPE(clut_3D) NOPE(clut_4D)
|
||||
|
@ -100,12 +100,15 @@ struct SkJumper_GradientCtx {
|
||||
float* ts;
|
||||
};
|
||||
|
||||
// TODO (liyuqian): remove fCoeffA, fInvCoeffA, fR0, fDR once rebaselined
|
||||
struct SkJumper_2PtConicalCtx {
|
||||
uint32_t fMask[SkJumper_kMaxStride];
|
||||
float fCoeffA,
|
||||
fInvCoeffA,
|
||||
fR0,
|
||||
fDR;
|
||||
fDR,
|
||||
fP0,
|
||||
fP1;
|
||||
};
|
||||
|
||||
struct SkJumper_UniformColorCtx {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -991,8 +991,6 @@ STAGE(store_rgba, float* ptr) {
|
||||
|
||||
SI F inv(F x) { return 1.0f - x; }
|
||||
SI F two(F x) { return x + x; }
|
||||
SI F first (F a, F b) { return a; }
|
||||
SI F second(F a, F b) { return b; }
|
||||
|
||||
|
||||
BLEND_MODE(clear) { return 0; }
|
||||
@ -1951,6 +1949,11 @@ STAGE(xy_to_radius, Ctx::None) {
|
||||
r = sqrt_(X2 + Y2);
|
||||
}
|
||||
|
||||
// TODO (liyuqian): remove xy_to_2pt_conical_quadratic_first, xy_to_2pt_conical_quadratic_second,
|
||||
// xy_to_2pt_conical_linear, and mask_2pt_conical_degenerates_legacy once rebaselined.
|
||||
SI F first (F a, F b) { return a; }
|
||||
SI F second(F a, F b) { return b; }
|
||||
|
||||
SI F solve_2pt_conical_quadratic(const SkJumper_2PtConicalCtx* c, F x, F y, F (*select)(F, F)) {
|
||||
// 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)
|
||||
@ -1999,7 +2002,7 @@ STAGE(xy_to_2pt_conical_linear, const SkJumper_2PtConicalCtx* c) {
|
||||
r = -coeffC / coeffB;
|
||||
}
|
||||
|
||||
STAGE(mask_2pt_conical_degenerates, SkJumper_2PtConicalCtx* c) {
|
||||
STAGE(mask_2pt_conical_degenerates_legacy, SkJumper_2PtConicalCtx* c) {
|
||||
// The gradient t coordinate is in the r register right now.
|
||||
F& t = r;
|
||||
|
||||
@ -2011,6 +2014,59 @@ STAGE(mask_2pt_conical_degenerates, SkJumper_2PtConicalCtx* c) {
|
||||
unaligned_store(&c->fMask, if_then_else(is_degenerate, U32(0), U32(0xffffffff)));
|
||||
}
|
||||
|
||||
// Please see https://skia.org/dev/design/conical for how our 2pt conical shader works.
|
||||
|
||||
STAGE(negate_x, Ctx::None) { r = -r; }
|
||||
|
||||
STAGE(xy_to_2pt_conical_strip, const SkJumper_2PtConicalCtx* ctx) {
|
||||
F x = r, y = g, &t = r;
|
||||
t = x + sqrt_(ctx->fP0 - y*y); // ctx->fP0 = r0 * r0
|
||||
}
|
||||
|
||||
STAGE(xy_to_2pt_conical_focal_on_circle, Ctx::None) {
|
||||
F x = r, y = g, &t = r;
|
||||
t = x + y*y / x; // (x^2 + y^2) / x
|
||||
}
|
||||
|
||||
STAGE(xy_to_2pt_conical_well_behaved, const SkJumper_2PtConicalCtx* ctx) {
|
||||
F x = r, y = g, &t = r;
|
||||
t = sqrt_(x*x + y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1
|
||||
}
|
||||
|
||||
STAGE(xy_to_2pt_conical_greater, const SkJumper_2PtConicalCtx* ctx) {
|
||||
F x = r, y = g, &t = r;
|
||||
t = sqrt_(x*x - y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1
|
||||
}
|
||||
|
||||
STAGE(xy_to_2pt_conical_smaller, const SkJumper_2PtConicalCtx* ctx) {
|
||||
F x = r, y = g, &t = r;
|
||||
t = -sqrt_(x*x - y*y) - x * ctx->fP0; // ctx->fP0 = 1/r1
|
||||
}
|
||||
|
||||
STAGE(alter_2pt_conical_compensate_focal, const SkJumper_2PtConicalCtx* ctx) {
|
||||
F& t = r;
|
||||
t = t + ctx->fP1; // ctx->fP1 = f
|
||||
}
|
||||
|
||||
STAGE(alter_2pt_conical_unswap, Ctx::None) {
|
||||
F& t = r;
|
||||
t = 1 - t;
|
||||
}
|
||||
|
||||
STAGE(mask_2pt_conical_nan, SkJumper_2PtConicalCtx* c) {
|
||||
F& t = r;
|
||||
auto is_degenerate = (t != t); // NaN
|
||||
t = if_then_else(is_degenerate, F(0), t);
|
||||
unaligned_store(&c->fMask, if_then_else(is_degenerate, U32(0), U32(0xffffffff)));
|
||||
}
|
||||
|
||||
STAGE(mask_2pt_conical_degenerates, SkJumper_2PtConicalCtx* c) {
|
||||
F& t = r;
|
||||
auto is_degenerate = (t <= 0) | (t != t);
|
||||
t = if_then_else(is_degenerate, F(0), t);
|
||||
unaligned_store(&c->fMask, if_then_else(is_degenerate, U32(0), U32(0xffffffff)));
|
||||
}
|
||||
|
||||
STAGE(apply_vector_mask, const uint32_t* ctx) {
|
||||
const U32 mask = unaligned_load<U32>(ctx);
|
||||
r = bit_cast<F>(bit_cast<U32>(r) & mask);
|
||||
|
@ -12,6 +12,49 @@
|
||||
#include "SkWriteBuffer.h"
|
||||
#include "../../jumper/SkJumper.h"
|
||||
|
||||
// Please see https://skia.org/dev/design/conical for how our shader works.
|
||||
|
||||
void SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix& matrix) {
|
||||
#ifdef SK_SUPPORT_LEGACY_2PT_CONICAL
|
||||
// Just initialize the memory. We are not supposed to do anything in legacy mode.
|
||||
fIsSwapped = false;
|
||||
fFocalX = fR1 = 0;
|
||||
#else
|
||||
fIsSwapped = false;
|
||||
fFocalX = r0 / (r0 - r1);
|
||||
if (SkScalarNearlyZero(fFocalX - 1)) {
|
||||
// swap r0, r1
|
||||
matrix.postTranslate(-1, 0);
|
||||
matrix.postScale(-1, 1);
|
||||
std::swap(r0, r1);
|
||||
fFocalX = 0; // because r0 is now 0
|
||||
fIsSwapped = true;
|
||||
}
|
||||
|
||||
// Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
|
||||
const SkPoint from[2] = { {fFocalX, 0}, {1, 0} };
|
||||
const SkPoint to[2] = { {0, 0}, {1, 0} };
|
||||
SkMatrix focalMatrix;
|
||||
if (!focalMatrix.setPolyToPoly(from, to, 2)) {
|
||||
SkDEBUGFAILF("Mapping focal point failed unexpectedly for focalX = %f.\n", fFocalX);
|
||||
// We won't be able to draw the gradient; at least make sure that we initialize the
|
||||
// memory to prevent security issues.
|
||||
focalMatrix = SkMatrix::MakeScale(1, 1);
|
||||
}
|
||||
matrix.postConcat(focalMatrix);
|
||||
fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
|
||||
|
||||
// The following transformations are just to accelerate the shader computation by saving
|
||||
// some arithmatic operations.
|
||||
if (this->isFocalOnCircle()) {
|
||||
matrix.postScale(0.5, 0.5);
|
||||
} else {
|
||||
matrix.postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
|
||||
}
|
||||
matrix.postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
|
||||
#endif
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
|
||||
const SkPoint& c1, SkScalar r1,
|
||||
const Descriptor& desc) {
|
||||
@ -34,18 +77,22 @@ sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// General two-point case.
|
||||
gradientType = Type::kTwoPoint;
|
||||
gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
|
||||
}
|
||||
|
||||
FocalData focalData;
|
||||
if (gradientType == Type::kFocal) {
|
||||
const auto dCenter = (c0 - c1).length();
|
||||
focalData.set(r0 / dCenter, r1 / dCenter, gradientMatrix); // this may change gradientMatrix
|
||||
}
|
||||
return sk_sp<SkShader>(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc,
|
||||
gradientType, gradientMatrix));
|
||||
gradientType, gradientMatrix, focalData));
|
||||
}
|
||||
|
||||
SkTwoPointConicalGradient::SkTwoPointConicalGradient(
|
||||
const SkPoint& start, SkScalar startRadius,
|
||||
const SkPoint& end, SkScalar endRadius,
|
||||
const Descriptor& desc, Type type, const SkMatrix& gradientMatrix)
|
||||
const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
|
||||
: SkGradientShaderBase(desc, gradientMatrix)
|
||||
, fCenter1(start)
|
||||
, fCenter2(end)
|
||||
@ -55,6 +102,9 @@ SkTwoPointConicalGradient::SkTwoPointConicalGradient(
|
||||
{
|
||||
// this is degenerate, and should be caught by our caller
|
||||
SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
|
||||
if (type == Type::kFocal) {
|
||||
fFocalData = data;
|
||||
}
|
||||
}
|
||||
|
||||
bool SkTwoPointConicalGradient::isOpaque() const {
|
||||
@ -189,6 +239,7 @@ void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRast
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef SK_SUPPORT_LEGACY_2PT_CONICAL
|
||||
const auto dCenter = (fCenter1 - fCenter2).length();
|
||||
|
||||
// Since we've squashed the centers into a unit vector, we must also scale
|
||||
@ -230,7 +281,48 @@ void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRast
|
||||
}
|
||||
|
||||
if (!isWellBehaved) {
|
||||
p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
|
||||
p->append(SkRasterPipeline::mask_2pt_conical_degenerates_legacy, ctx);
|
||||
postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
|
||||
}
|
||||
#else
|
||||
if (fType == Type::kStrip) {
|
||||
auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
|
||||
SkScalar scaledR0 = fRadius1 / this->getCenterX1();
|
||||
ctx->fP0 = scaledR0 * scaledR0;
|
||||
p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
|
||||
p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
|
||||
postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
|
||||
ctx->fP0 = 1/fFocalData.fR1;
|
||||
ctx->fP1 = fFocalData.fFocalX;
|
||||
|
||||
if (fFocalData.isFocalOnCircle()) {
|
||||
p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
|
||||
} else if (fFocalData.isWellBehaved()) {
|
||||
p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
|
||||
} else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
|
||||
p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
|
||||
} else {
|
||||
p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
|
||||
}
|
||||
|
||||
if (!fFocalData.isWellBehaved()) {
|
||||
p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
|
||||
}
|
||||
if (1 - fFocalData.fFocalX < 0) {
|
||||
p->append(SkRasterPipeline::negate_x);
|
||||
}
|
||||
if (!fFocalData.isNativelyFocal()) {
|
||||
p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
|
||||
}
|
||||
if (fFocalData.isSwapped()) {
|
||||
p->append(SkRasterPipeline::alter_2pt_conical_unswap);
|
||||
}
|
||||
if (!fFocalData.isWellBehaved()) {
|
||||
postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -13,6 +13,30 @@
|
||||
|
||||
class SkTwoPointConicalGradient final : public SkGradientShaderBase {
|
||||
public:
|
||||
// See https://skia.org/dev/design/conical for what focal data means and how our shader works.
|
||||
// We make it public so the GPU shader can also use it.
|
||||
struct FocalData {
|
||||
SkScalar fR1; // r1 after mapping focal point to (0, 0)
|
||||
SkScalar fFocalX; // f
|
||||
bool fIsSwapped; // whether we swapped r0, r1
|
||||
|
||||
// The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}.
|
||||
// We'll post concat matrix with our transformation matrix that maps focal point to (0, 0).
|
||||
void set(SkScalar r0, SkScalar r1, SkMatrix& matrix);
|
||||
|
||||
// Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If
|
||||
// this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves)
|
||||
// will go through the focal point (aircraft). In our previous implementations, this was
|
||||
// known as the edge case where the inside circle touches the outside circle (on the focal
|
||||
// point). If we were to solve for t bruteforcely using a quadratic equation, this case
|
||||
// implies that the quadratic equation degenerates to a linear equation.
|
||||
bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); }
|
||||
|
||||
bool isSwapped() const { return fIsSwapped; }
|
||||
bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; }
|
||||
bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); }
|
||||
};
|
||||
|
||||
static sk_sp<SkShader> Create(const SkPoint& start, SkScalar startRadius,
|
||||
const SkPoint& end, SkScalar endRadius,
|
||||
const Descriptor&);
|
||||
@ -45,12 +69,13 @@ protected:
|
||||
private:
|
||||
enum class Type {
|
||||
kRadial,
|
||||
kTwoPoint,
|
||||
kStrip,
|
||||
kFocal
|
||||
};
|
||||
|
||||
SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0,
|
||||
const SkPoint& c1, SkScalar r1,
|
||||
const Descriptor&, Type, const SkMatrix&);
|
||||
const Descriptor&, Type, const SkMatrix&, const FocalData&);
|
||||
|
||||
SkPoint fCenter1;
|
||||
SkPoint fCenter2;
|
||||
@ -58,6 +83,8 @@ private:
|
||||
SkScalar fRadius2;
|
||||
Type fType;
|
||||
|
||||
FocalData fFocalData;
|
||||
|
||||
friend class SkGradientShader;
|
||||
typedef SkGradientShaderBase INHERITED;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user