blob: 98433e28eff7cf9d6785efcdc6b2a2b50a480d6a [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkRadialGradient.h"
#include "SkRadialGradient_Table.h"
#define kSQRT_TABLE_BITS 11
#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
#include <stdio.h>
void SkRadialGradient_BuildTable() {
// build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
SkASSERT(file);
::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
if ((i & 15) == 0) {
::fprintf(file, "\t");
}
uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
::fprintf(file, "0x%02X", value);
if (i < kSQRT_TABLE_SIZE-1) {
::fprintf(file, ", ");
}
if ((i & 15) == 15) {
::fprintf(file, "\n");
}
}
::fprintf(file, "};\n");
::fclose(file);
}
#endif
namespace {
void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
SkMatrix* matrix) {
SkScalar inv = SkScalarInvert(radius);
matrix->setTranslate(-center.fX, -center.fY);
matrix->postScale(inv, inv);
}
typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
uint16_t* dstC, const uint16_t* cache,
int toggle, int count);
void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
int toggle, int count) {
const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
/* knock these down so we can pin against +- 0x7FFF, which is an
immediate load, rather than 0xFFFF which is slower. This is a
compromise, since it reduces our precision, but that appears
to be visually OK. If we decide this is OK for all of our cases,
we could (it seems) put this scale-down into fDstToIndex,
to avoid having to do these extra shifts each time.
*/
SkFixed fx = SkScalarToFixed(sfx) >> 1;
SkFixed dx = SkScalarToFixed(sdx) >> 1;
SkFixed fy = SkScalarToFixed(sfy) >> 1;
SkFixed dy = SkScalarToFixed(sdy) >> 1;
// might perform this check for the other modes,
// but the win will be a smaller % of the total
if (dy == 0) {
fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fy *= fy;
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
fx += dx;
*dstC++ = cache[toggle +
(sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
toggle ^= SkGradientShaderBase::kDitherStride16;
} while (--count != 0);
} else {
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
fx += dx;
fy += dy;
*dstC++ = cache[toggle +
(sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
toggle ^= SkGradientShaderBase::kDitherStride16;
} while (--count != 0);
}
}
void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
int toggle, int count) {
do {
#ifdef SK_SCALAR_IS_FLOAT
float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
SkFixed dist = SkFloatToFixed(fdist);
#else
SkFixed magnitudeSquared = SkFixedSquare(sfx) +
SkFixedSquare(sfy);
if (magnitudeSquared < 0) // Overflow.
magnitudeSquared = SK_FixedMax;
SkFixed dist = SkFixedSqrt(magnitudeSquared);
#endif
unsigned fi = mirror_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
toggle ^= SkGradientShaderBase::kDitherStride16;
sfx += sdx;
sfy += sdy;
} while (--count != 0);
}
void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
int toggle, int count) {
SkFixed fx = SkScalarToFixed(sfx);
SkFixed dx = SkScalarToFixed(sdx);
SkFixed fy = SkScalarToFixed(sfy);
SkFixed dy = SkScalarToFixed(sdy);
do {
SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
unsigned fi = repeat_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
fx += dx;
fy += dy;
*dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
toggle ^= SkGradientShaderBase::kDitherStride16;
} while (--count != 0);
}
}
/////////////////////////////////////////////////////////////////////
SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius,
const SkColor colors[], const SkScalar pos[], int colorCount,
SkShader::TileMode mode, SkUnitMapper* mapper)
: SkGradientShaderBase(colors, pos, colorCount, mode, mapper),
fCenter(center),
fRadius(radius)
{
// make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
rad_to_unit_matrix(center, radius, &fPtsToUnit);
}
void SkRadialGradient::shadeSpan16(int x, int y, uint16_t* dstCParam,
int count) {
SkASSERT(count > 0);
uint16_t* SK_RESTRICT dstC = dstCParam;
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const uint16_t* SK_RESTRICT cache = this->getCache16();
int toggle = ((x ^ y) & 1) * kDitherStride16;
if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
SkScalar sdx = fDstToIndex.getScaleX();
SkScalar sdy = fDstToIndex.getSkewY();
if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
SkFixed storage[2];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
&storage[0], &storage[1]);
sdx = SkFixedToScalar(storage[0]);
sdy = SkFixedToScalar(storage[1]);
} else {
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
}
RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
if (SkShader::kClamp_TileMode == fTileMode) {
shadeProc = shadeSpan16_radial_clamp;
} else if (SkShader::kMirror_TileMode == fTileMode) {
shadeProc = shadeSpan16_radial_mirror;
} else {
SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
}
(*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
cache, toggle, count);
} else { // perspective case
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.length()));
SkASSERT(fi <= 0xFFFF);
int index = fi >> (16 - kCache16Bits);
*dstC++ = cache[toggle + index];
toggle ^= kDitherStride16;
dstX += SK_Scalar1;
} while (--count != 0);
}
}
SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
SkMatrix* matrix, SkShader::TileMode* xy) const {
if (bitmap) {
this->getGradientTableBitmap(bitmap);
}
if (matrix) {
matrix->setScale(SkIntToScalar(kGradient32Length),
SkIntToScalar(kGradient32Length));
matrix->preConcat(fPtsToUnit);
}
if (xy) {
xy[0] = fTileMode;
xy[1] = kClamp_TileMode;
}
return kRadial_BitmapType;
}
SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
if (info) {
commonAsAGradient(info);
info->fPoint[0] = fCenter;
info->fRadius[0] = fRadius;
}
return kRadial_GradientType;
}
SkRadialGradient::SkRadialGradient(SkFlattenableReadBuffer& buffer)
: INHERITED(buffer),
fCenter(buffer.readPoint()),
fRadius(buffer.readScalar()) {
}
void SkRadialGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writePoint(fCenter);
buffer.writeScalar(fRadius);
}
namespace {
inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
// fast, overly-conservative test: checks unit square instead
// of unit circle
bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
(fx <= -SK_FixedHalf && dx <= 0);
bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
(fy <= -SK_FixedHalf && dy <= 0);
return xClamped || yClamped;
}
// Return true if (fx * fy) is always inside the unit circle
// SkPin32 is expensive, but so are all the SkFixedMul in this test,
// so it shouldn't be run if count is small.
inline bool no_need_for_radial_pin(int fx, int dx,
int fy, int dy, int count) {
SkASSERT(count > 0);
if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
return false;
}
if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
return false;
}
fx += (count - 1) * dx;
fy += (count - 1) * dy;
if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
return false;
}
return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
}
#define UNPINNED_RADIAL_STEP \
fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
*dstC++ = cache[toggle + \
(sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
toggle ^= SkGradientShaderBase::kDitherStride32; \
fx += dx; \
fy += dy;
typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
SkPMColor* dstC, const SkPMColor* cache,
int count, int toggle);
// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
int count, int toggle) {
// Floating point seems to be slower than fixed point,
// even when we have float hardware.
const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
SkFixed fx = SkScalarToFixed(sfx) >> 1;
SkFixed dx = SkScalarToFixed(sdx) >> 1;
SkFixed fy = SkScalarToFixed(sfy) >> 1;
SkFixed dy = SkScalarToFixed(sdy) >> 1;
if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
unsigned fi = SkGradientShaderBase::kGradient32Length;
sk_memset32_dither(dstC,
cache[toggle + fi],
cache[(toggle ^ SkGradientShaderBase::kDitherStride32) + fi],
count);
} else if ((count > 4) &&
no_need_for_radial_pin(fx, dx, fy, dy, count)) {
unsigned fi;
// 4x unroll appears to be no faster than 2x unroll on Linux
while (count > 1) {
UNPINNED_RADIAL_STEP;
UNPINNED_RADIAL_STEP;
count -= 2;
}
if (count) {
UNPINNED_RADIAL_STEP;
}
}
else {
// Specializing for dy == 0 gains us 25% on Skia benchmarks
if (dy == 0) {
unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
yy *= yy;
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
*dstC++ = cache[toggle + (sqrt_table[fi] >>
SkGradientShaderBase::kSqrt32Shift)];
toggle ^= SkGradientShaderBase::kDitherStride32;
fx += dx;
} while (--count != 0);
} else {
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
*dstC++ = cache[toggle + (sqrt_table[fi] >>
SkGradientShaderBase::kSqrt32Shift)];
toggle ^= SkGradientShaderBase::kDitherStride32;
fx += dx;
fy += dy;
} while (--count != 0);
}
}
}
// Unrolling this loop doesn't seem to help (when float); we're stalling to
// get the results of the sqrt (?), and don't have enough extra registers to
// have many in flight.
void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
int count, int toggle) {
do {
#ifdef SK_SCALAR_IS_FLOAT
float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
SkFixed dist = SkFloatToFixed(fdist);
#else
SkFixed magnitudeSquared = SkFixedSquare(sfx) +
SkFixedSquare(sfy);
if (magnitudeSquared < 0) // Overflow.
magnitudeSquared = SK_FixedMax;
SkFixed dist = SkFixedSqrt(magnitudeSquared);
#endif
unsigned fi = mirror_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
toggle ^= SkGradientShaderBase::kDitherStride32;
sfx += sdx;
sfy += sdy;
} while (--count != 0);
}
void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
SkScalar sfy, SkScalar sdy,
SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
int count, int toggle) {
SkFixed fx = SkScalarToFixed(sfx);
SkFixed dx = SkScalarToFixed(sdx);
SkFixed fy = SkScalarToFixed(sfy);
SkFixed dy = SkScalarToFixed(sdy);
do {
SkFixed magnitudeSquared = SkFixedSquare(fx) +
SkFixedSquare(fy);
if (magnitudeSquared < 0) // Overflow.
magnitudeSquared = SK_FixedMax;
SkFixed dist = SkFixedSqrt(magnitudeSquared);
unsigned fi = repeat_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
toggle ^= SkGradientShaderBase::kDitherStride32;
fx += dx;
fy += dy;
} while (--count != 0);
}
}
void SkRadialGradient::shadeSpan(int x, int y,
SkPMColor* SK_RESTRICT dstC, int count) {
SkASSERT(count > 0);
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const SkPMColor* SK_RESTRICT cache = this->getCache32();
#ifdef USE_DITHER_32BIT_GRADIENT
int toggle = ((x ^ y) & 1) * SkGradientShaderBase::kDitherStride32;
#else
int toggle = 0;
#endif
if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
SkScalar sdx = fDstToIndex.getScaleX();
SkScalar sdy = fDstToIndex.getSkewY();
if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
SkFixed storage[2];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
&storage[0], &storage[1]);
sdx = SkFixedToScalar(storage[0]);
sdy = SkFixedToScalar(storage[1]);
} else {
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
}
RadialShadeProc shadeProc = shadeSpan_radial_repeat;
if (SkShader::kClamp_TileMode == fTileMode) {
shadeProc = shadeSpan_radial_clamp;
} else if (SkShader::kMirror_TileMode == fTileMode) {
shadeProc = shadeSpan_radial_mirror;
} else {
SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
}
(*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
} else { // perspective case
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.length()));
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
dstX += SK_Scalar1;
} while (--count != 0);
}
}
/////////////////////////////////////////////////////////////////////
#if SK_SUPPORT_GPU
class GrGLRadialGradient : public GrGLGradientStage {
public:
GrGLRadialGradient(const GrProgramStageFactory& factory,
const GrCustomStage&) : INHERITED (factory) { }
virtual ~GrGLRadialGradient() { }
virtual void emitVS(GrGLShaderBuilder* builder,
const char* vertexCoords) SK_OVERRIDE { }
virtual void emitFS(GrGLShaderBuilder* builder,
const char* outputColor,
const char* inputColor,
const char* samplerName) SK_OVERRIDE;
static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps) { return 0; }
private:
typedef GrGLGradientStage INHERITED;
};
/////////////////////////////////////////////////////////////////////
class GrRadialGradient : public GrGradientEffect {
public:
GrRadialGradient(GrContext* ctx,
const SkRadialGradient& shader,
GrSamplerState* sampler)
: INHERITED(ctx, shader, sampler) {
}
virtual ~GrRadialGradient() { }
static const char* Name() { return "Radial Gradient"; }
virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
return GrTProgramStageFactory<GrRadialGradient>::getInstance();
}
typedef GrGLRadialGradient GLProgramStage;
private:
GR_DECLARE_CUSTOM_STAGE_TEST;
typedef GrGradientEffect INHERITED;
};
/////////////////////////////////////////////////////////////////////
GR_DEFINE_CUSTOM_STAGE_TEST(GrRadialGradient);
GrCustomStage* GrRadialGradient::TestCreate(SkRandom* random,
GrContext* context,
GrTexture**) {
SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
SkScalar radius = random->nextUScalar1();
SkColor colors[kMaxRandomGradientColors];
SkScalar stopsArray[kMaxRandomGradientColors];
SkScalar* stops = stopsArray;
SkShader::TileMode tm;
int colorCount = RandomGradientParams(random, colors, &stops, &tm);
SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
colors, stops, colorCount,
tm));
GrSamplerState sampler;
GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
GrAssert(NULL != stage);
return stage;
}
/////////////////////////////////////////////////////////////////////
void GrGLRadialGradient::emitFS(GrGLShaderBuilder* builder,
const char* outputColor,
const char* inputColor,
const char* samplerName) {
SkString t;
t.printf("length(%s.xy)", builder->defaultTexCoordsName());
this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplerName);
}
/////////////////////////////////////////////////////////////////////
GrCustomStage* SkRadialGradient::asNewCustomStage(GrContext* context,
GrSamplerState* sampler) const {
SkASSERT(NULL != context && NULL != sampler);
sampler->matrix()->preConcat(fPtsToUnit);
sampler->textureParams()->setTileModeX(fTileMode);
sampler->textureParams()->setTileModeY(kClamp_TileMode);
sampler->textureParams()->setBilerp(true);
return SkNEW_ARGS(GrRadialGradient, (context, *this, sampler));
}
#else
GrCustomStage* SkRadialGradient::asNewCustomStage(GrContext* context,
GrSamplerState* sampler) const {
SkDEBUGFAIL("Should not call in GPU-less build");
return NULL;
}
#endif