/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrColor.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrMeshDrawOp.h"
#include "GrOpFlushState.h"
#include "GrRectOpFactory.h"
#include "GrResourceKey.h"
#include "GrResourceProvider.h"
#include "GrTypes.h"
#include "SkMatrix.h"
#include "SkMatrixPriv.h"
#include "SkRect.h"
#include "SkPointPriv.h"
#include "ops/GrSimpleMeshDrawOpHelper.h"
GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
static inline bool view_matrix_ok_for_aa_fill_rect(const SkMatrix& viewMatrix) {
return viewMatrix.preservesRightAngles();
}
static inline void set_inset_fan(SkPoint* pts, size_t stride, const SkRect& r, SkScalar dx,
SkScalar dy) {
SkPointPriv::SetRectFan(pts, r.fLeft + dx, r.fTop + dy, r.fRight - dx, r.fBottom - dy, stride);
}
static const int kNumAAFillRectsInIndexBuffer = 256;
static const int kVertsPerAAFillRect = 8;
static const int kIndicesPerAAFillRect = 30;
static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
// clang-format off
static const uint16_t gFillAARectIdx[] = {
0, 1, 5, 5, 4, 0,
1, 2, 6, 6, 5, 1,
2, 3, 7, 7, 6, 2,
3, 0, 4, 4, 7, 3,
4, 5, 6, 6, 7, 4,
};
// clang-format on
GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
return resourceProvider->findOrCreatePatternedIndexBuffer(
gFillAARectIdx, kIndicesPerAAFillRect, kNumAAFillRectsInIndexBuffer,
kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
}
static void generate_aa_fill_rect_geometry(intptr_t verts,
size_t vertexStride,
GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect& devRect,
bool tweakAlphaForCoverage,
const SkMatrix* localMatrix) {
SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vertexStride);
SkScalar inset;
if (viewMatrix.rectStaysRect()) {
inset = SkMinScalar(devRect.width(), SK_Scalar1);
inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height());
set_inset_fan(fan0Pos, vertexStride, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
set_inset_fan(fan1Pos, vertexStride, devRect, inset, inset);
} else {
// compute transformed (1, 0) and (0, 1) vectors
SkVector vec[2] = {{viewMatrix[SkMatrix::kMScaleX], viewMatrix[SkMatrix::kMSkewY]},
{viewMatrix[SkMatrix::kMSkewX], viewMatrix[SkMatrix::kMScaleY]}};
SkScalar len1 = SkPoint::Normalize(&vec[0]);
vec[0].scale(SK_ScalarHalf);
SkScalar len2 = SkPoint::Normalize(&vec[1]);
vec[1].scale(SK_ScalarHalf);
inset = SkMinScalar(len1 * rect.width(), SK_Scalar1);
inset = SK_ScalarHalf * SkMinScalar(inset, len2 * rect.height());
// create the rotated rect
SkPointPriv::SetRectFan(fan0Pos, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
vertexStride);
SkMatrixPriv::MapPointsWithStride(viewMatrix, fan0Pos, vertexStride, 4);
// Now create the inset points and then outset the original
// rotated points
// TL
*((SkPoint*)((intptr_t)fan1Pos + 0 * vertexStride)) =
*((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) + vec[0] + vec[1];
*((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) -= vec[0] + vec[1];
// BL
*((SkPoint*)((intptr_t)fan1Pos + 1 * vertexStride)) =
*((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) + vec[0] - vec[1];
*((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) -= vec[0] - vec[1];
// BR
*((SkPoint*)((intptr_t)fan1Pos + 2 * vertexStride)) =
*((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) - vec[0] - vec[1];
*((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) += vec[0] + vec[1];
// TR
*((SkPoint*)((intptr_t)fan1Pos + 3 * vertexStride)) =
*((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) - vec[0] + vec[1];
*((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) += vec[0] - vec[1];
}
if (localMatrix) {
SkMatrix invViewMatrix;
if (!viewMatrix.invert(&invViewMatrix)) {
SkDebugf("View matrix is non-invertible, local coords will be wrong.");
invViewMatrix = SkMatrix::I();
}
SkMatrix localCoordMatrix;
localCoordMatrix.setConcat(*localMatrix, invViewMatrix);
SkPoint* fan0Loc = reinterpret_cast<SkPoint*>(verts + sizeof(SkPoint) + sizeof(GrColor));
SkMatrixPriv::MapPointsWithStride(localCoordMatrix, fan0Loc, vertexStride, fan0Pos,
vertexStride, 8);
}
// Make verts point to vertex color and then set all the color and coverage vertex attrs
// values.
verts += sizeof(SkPoint);
// The coverage offset is always the last vertex attribute
intptr_t coverageOffset = vertexStride - sizeof(GrColor) - sizeof(SkPoint);
for (int i = 0; i < 4; ++i) {
if (tweakAlphaForCoverage) {
*reinterpret_cast<GrColor*>(verts + i * vertexStride) = 0;
} else {
*reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
*reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = 0;
}
}
int scale;
if (inset < SK_ScalarHalf) {
scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
SkASSERT(scale >= 0 && scale <= 255);
} else {
scale = 0xff;
}
verts += 4 * vertexStride;
float innerCoverage = GrNormalizeByteToFloat(scale);
GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
for (int i = 0; i < 4; ++i) {
if (tweakAlphaForCoverage) {
*reinterpret_cast<GrColor*>(verts + i * vertexStride) = scaledColor;
} else {
*reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
*reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = innerCoverage;
}
}
}
namespace {
class AAFillRectOp final : public GrMeshDrawOp {
private:
using Helper = GrSimpleMeshDrawOpHelperWithStencil;
public:
DEFINE_OP_CLASS_ID
static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect& devRect,
const SkMatrix* localMatrix,
const GrUserStencilSettings* stencil) {
SkASSERT(view_matrix_ok_for_aa_fill_rect(viewMatrix));
return Helper::FactoryHelper<AAFillRectOp>(std::move(paint), viewMatrix, rect, devRect,
localMatrix, stencil);
}
AAFillRectOp(const Helper::MakeArgs& helperArgs,
GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect& devRect,
const SkMatrix* localMatrix,
const GrUserStencilSettings* stencil)
: INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencil) {
if (localMatrix) {
void* mem = fRectData.push_back_n(sizeof(RectWithLocalMatrixInfo));
new (mem) RectWithLocalMatrixInfo(color, viewMatrix, rect, devRect, *localMatrix);
} else {
void* mem = fRectData.push_back_n(sizeof(RectInfo));
new (mem) RectInfo(color, viewMatrix, rect, devRect);
}
IsZeroArea zeroArea =
(!rect.width() || !rect.height()) ? IsZeroArea::kYes : IsZeroArea::kNo;
this->setBounds(devRect, HasAABloat::kYes, zeroArea);
fRectCnt = 1;
}
const char* name() const override { return "AAFillRectOp"; }
void visitProxies(const VisitProxyFunc& func) const override {
fHelper.visitProxies(func);
}
SkString dumpInfo() const override {
SkString str;
str.append(INHERITED::dumpInfo());
str.appendf("# combined: %d\n", fRectCnt);
const RectInfo* info = this->first();
for (int i = 0; i < fRectCnt; ++i) {
const SkRect& rect = info->rect();
str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
info->color(), rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
info = this->next(info);
}
str += fHelper.dumpInfo();
str += INHERITED::dumpInfo();
return str;
}
FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
GrPixelConfigIsClamped dstIsClamped) override {
GrColor color = this->first()->color();
auto result = fHelper.xpRequiresDstTexture(
caps, clip, dstIsClamped, GrProcessorAnalysisCoverage::kSingleChannel, &color);
this->first()->setColor(color);
return result;
}
private:
void onPrepareDraws(Target* target) override {
using namespace GrDefaultGeoProcFactory;
Color color(Color::kPremulGrColorAttribute_Type);
Coverage::Type coverageType = fHelper.compatibleWithAlphaAsCoverage()
? Coverage::kSolid_Type
: Coverage::kAttribute_Type;
LocalCoords lc = fHelper.usesLocalCoords() ? LocalCoords::kHasExplicit_Type
: LocalCoords::kUnused_Type;
sk_sp<GrGeometryProcessor> gp =
GrDefaultGeoProcFactory::Make(color, coverageType, lc, SkMatrix::I());
if (!gp) {
SkDebugf("Couldn't create GrGeometryProcessor\n");
return;
}
size_t vertexStride = gp->getVertexStride();
sk_sp<const GrBuffer> indexBuffer = get_index_buffer(target->resourceProvider());
PatternHelper helper(GrPrimitiveType::kTriangles);
void* vertices =
helper.init(target, vertexStride, indexBuffer.get(), kVertsPerAAFillRect,
kIndicesPerAAFillRect, fRectCnt);
if (!vertices || !indexBuffer) {
SkDebugf("Could not allocate vertices\n");
return;
}
const RectInfo* info = this->first();
const SkMatrix* localMatrix = nullptr;
for (int i = 0; i < fRectCnt; i++) {
intptr_t verts =
reinterpret_cast<intptr_t>(vertices) + i * kVertsPerAAFillRect * vertexStride;
if (fHelper.usesLocalCoords()) {
if (info->hasLocalMatrix()) {
localMatrix = &static_cast<const RectWithLocalMatrixInfo*>(info)->localMatrix();
} else {
localMatrix = &SkMatrix::I();
}
}
generate_aa_fill_rect_geometry(verts, vertexStride, info->color(), info->viewMatrix(),
info->rect(), info->devRect(),
fHelper.compatibleWithAlphaAsCoverage(), localMatrix);
info = this->next(info);
}
helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
}
bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
AAFillRectOp* that = t->cast<AAFillRectOp>();
if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
return false;
}
fRectData.push_back_n(that->fRectData.count(), that->fRectData.begin());
fRectCnt += that->fRectCnt;
this->joinBounds(*that);
return true;
}
struct RectInfo {
public:
RectInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
const SkRect& devRect)
: RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kNo) {}
bool hasLocalMatrix() const { return HasLocalMatrix::kYes == fHasLocalMatrix; }
GrColor color() const { return fColor; }
const SkMatrix& viewMatrix() const { return fViewMatrix; }
const SkRect& rect() const { return fRect; }
const SkRect& devRect() const { return fDevRect; }
void setColor(GrColor color) { fColor = color; }
protected:
enum class HasLocalMatrix : uint32_t { kNo, kYes };
RectInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
const SkRect& devRect, HasLocalMatrix hasLM)
: fHasLocalMatrix(hasLM)
, fColor(color)
, fViewMatrix(viewMatrix)
, fRect(rect)
, fDevRect(devRect) {}
HasLocalMatrix fHasLocalMatrix;
GrColor fColor;
SkMatrix fViewMatrix;
SkRect fRect;
SkRect fDevRect;
};
struct RectWithLocalMatrixInfo : public RectInfo {
public:
RectWithLocalMatrixInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
const SkRect& devRect, const SkMatrix& localMatrix)
: RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kYes)
, fLocalMatrix(localMatrix) {}
const SkMatrix& localMatrix() const { return fLocalMatrix; }
private:
SkMatrix fLocalMatrix;
};
RectInfo* first() { return reinterpret_cast<RectInfo*>(fRectData.begin()); }
const RectInfo* first() const { return reinterpret_cast<const RectInfo*>(fRectData.begin()); }
const RectInfo* next(const RectInfo* prev) const {
intptr_t next =
reinterpret_cast<intptr_t>(prev) +
(prev->hasLocalMatrix() ? sizeof(RectWithLocalMatrixInfo) : sizeof(RectInfo));
return reinterpret_cast<const RectInfo*>(next);
}
SkSTArray<4 * sizeof(RectWithLocalMatrixInfo), uint8_t, true> fRectData;
Helper fHelper;
int fRectCnt;
typedef GrMeshDrawOp INHERITED;
};
} // anonymous namespace
namespace GrRectOpFactory {
std::unique_ptr<GrDrawOp> MakeAAFill(GrPaint&& paint, const SkMatrix& viewMatrix,
const SkRect& rect, const GrUserStencilSettings* stencil) {
if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
return nullptr;
}
SkRect devRect;
viewMatrix.mapRect(&devRect, rect);
return AAFillRectOp::Make(std::move(paint), viewMatrix, rect, devRect, nullptr, stencil);
}
std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrPaint&& paint, const SkMatrix& viewMatrix,
const SkMatrix& localMatrix,
const SkRect& rect) {
if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
return nullptr;
}
SkRect devRect;
viewMatrix.mapRect(&devRect, rect);
return AAFillRectOp::Make(std::move(paint), viewMatrix, rect, devRect, &localMatrix, nullptr);
}
std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrPaint&& paint, const SkMatrix& viewMatrix,
const SkRect& rect, const SkRect& localRect) {
if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
return nullptr;
}
SkRect devRect;
viewMatrix.mapRect(&devRect, rect);
SkMatrix localMatrix;
if (!localMatrix.setRectToRect(rect, localRect, SkMatrix::kFill_ScaleToFit)) {
return nullptr;
}
return AAFillRectOp::Make(std::move(paint), viewMatrix, rect, devRect, &localMatrix, nullptr);
}
} // namespace GrRectOpFactory
///////////////////////////////////////////////////////////////////////////////////////////////////
#if GR_TEST_UTILS
#include "GrDrawOpTest.h"
GR_DRAW_OP_TEST_DEFINE(AAFillRectOp) {
SkMatrix viewMatrix;
do {
viewMatrix = GrTest::TestMatrixInvertible(random);
} while (!view_matrix_ok_for_aa_fill_rect(viewMatrix));
SkRect rect = GrTest::TestRect(random);
SkRect devRect;
viewMatrix.mapRect(&devRect, rect);
const SkMatrix* localMatrix = nullptr;
SkMatrix m;
if (random->nextBool()) {
m = GrTest::TestMatrix(random);
}
const GrUserStencilSettings* stencil =
random->nextBool() ? nullptr : GrGetRandomStencil(random, context);
return AAFillRectOp::Make(std::move(paint), viewMatrix, rect, devRect, localMatrix, stencil);
}
#endif