/*
* 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 "GrNonAAFillRectBatch.h"
#include "GrBatchFlushState.h"
#include "GrColor.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrPrimitiveProcessor.h"
#include "GrResourceProvider.h"
#include "GrTInstanceBatch.h"
#include "GrQuad.h"
#include "GrVertexBatch.h"
// Common functions
class NonAAFillRectBatchBase {
public:
static const int kVertsPerInstance = 4;
static const int kIndicesPerInstance = 6;
static void InitInvariantOutputCoverage(GrInitInvariantOutput* out) {
out->setKnownSingleComponent(0xff);
}
static const GrIndexBuffer* GetIndexBuffer(GrResourceProvider* rp) {
return rp->refQuadIndexBuffer();
}
template <typename Geometry>
static void SetBounds(const Geometry& geo, SkRect* outBounds) {
geo.fViewMatrix.mapRect(outBounds, geo.fRect);
}
template <typename Geometry>
static void UpdateBoundsAfterAppend(const Geometry& geo, SkRect* outBounds) {
SkRect bounds = geo.fRect;
geo.fViewMatrix.mapRect(&bounds);
outBounds->join(bounds);
}
};
/** We always use per-vertex colors so that rects can be batched across color changes. Sometimes
we have explicit local coords and sometimes not. We *could* always provide explicit local
coords and just duplicate the positions when the caller hasn't provided a local coord rect,
but we haven't seen a use case which frequently switches between local rect and no local
rect draws.
The vertex attrib order is always pos, color, [local coords].
*/
static const GrGeometryProcessor* create_gp(const SkMatrix& viewMatrix,
bool readsCoverage,
bool hasExplicitLocalCoords,
const SkMatrix* localMatrix) {
using namespace GrDefaultGeoProcFactory;
Color color(Color::kAttribute_Type);
Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Type);
// If we have perspective on the viewMatrix then we won't map on the CPU, nor will we map
// the local rect on the cpu (in case the localMatrix also has perspective).
// Otherwise, if we have a local rect, then we apply the localMatrix directly to the localRect
// to generate vertex local coords
if (viewMatrix.hasPerspective()) {
LocalCoords localCoords(hasExplicitLocalCoords ? LocalCoords::kHasExplicit_Type :
LocalCoords::kUsePosition_Type,
localMatrix);
return GrDefaultGeoProcFactory::Create(color, coverage, localCoords, viewMatrix);
} else if (hasExplicitLocalCoords) {
LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
return GrDefaultGeoProcFactory::Create(color, coverage, localCoords, SkMatrix::I());
} else {
LocalCoords localCoords(LocalCoords::kUsePosition_Type, localMatrix);
return GrDefaultGeoProcFactory::CreateForDeviceSpace(color, coverage, localCoords,
viewMatrix);
}
}
static void tesselate(intptr_t vertices,
size_t vertexStride,
GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const GrQuad* localQuad) {
SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
positions->setRectFan(rect.fLeft, rect.fTop,
rect.fRight, rect.fBottom, vertexStride);
if (!viewMatrix.hasPerspective()) {
viewMatrix.mapPointsWithStride(positions, vertexStride,
NonAAFillRectBatchBase::kVertsPerInstance);
}
// Setup local coords
// TODO we should only do this if local coords are being read
if (localQuad) {
static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
for (int i = 0; i < NonAAFillRectBatchBase::kVertsPerInstance; i++) {
SkPoint* coords = reinterpret_cast<SkPoint*>(vertices + kLocalOffset +
i * vertexStride);
*coords = localQuad->point(i);
}
}
static const int kColorOffset = sizeof(SkPoint);
GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset);
for (int j = 0; j < 4; ++j) {
*vertColor = color;
vertColor = (GrColor*) ((intptr_t) vertColor + vertexStride);
}
}
class NonAAFillRectBatchImp : public NonAAFillRectBatchBase {
public:
struct Geometry {
SkMatrix fViewMatrix;
SkRect fRect;
GrQuad fLocalQuad;
GrColor fColor;
};
static const char* Name() { return "NonAAFillRectBatch"; }
static SkString DumpInfo(const Geometry& geo, int index) {
SkString str;
str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n",
index,
geo.fColor,
geo.fRect.fLeft, geo.fRect.fTop, geo.fRect.fRight, geo.fRect.fBottom);
return str;
}
static bool CanCombine(const Geometry& mine, const Geometry& theirs,
const GrXPOverridesForBatch& overrides) {
return true;
}
static const GrGeometryProcessor* CreateGP(const Geometry& geo,
const GrXPOverridesForBatch& overrides) {
const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, overrides.readsCoverage(), true,
nullptr);
SkASSERT(gp->getVertexStride() ==
sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr));
return gp;
}
static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry& geo,
const GrXPOverridesForBatch& overrides) {
tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect, &geo.fLocalQuad);
}
};
// We handle perspective in the local matrix or viewmatrix with special batches
class NonAAFillRectBatchPerspectiveImp : public NonAAFillRectBatchBase {
public:
struct Geometry {
SkMatrix fViewMatrix;
SkMatrix fLocalMatrix;
SkRect fRect;
SkRect fLocalRect;
GrColor fColor;
bool fHasLocalMatrix;
bool fHasLocalRect;
};
static const char* Name() { return "NonAAFillRectBatchPerspective"; }
static SkString DumpInfo(const Geometry& geo, int index) {
SkString str;
str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n",
index,
geo.fColor,
geo.fRect.fLeft, geo.fRect.fTop, geo.fRect.fRight, geo.fRect.fBottom);
return str;
}
static bool CanCombine(const Geometry& mine, const Geometry& theirs,
const GrXPOverridesForBatch& overrides) {
// We could batch across perspective vm changes if we really wanted to
return mine.fViewMatrix.cheapEqualTo(theirs.fViewMatrix) &&
mine.fHasLocalRect == theirs.fHasLocalRect &&
(!mine.fHasLocalMatrix || mine.fLocalMatrix.cheapEqualTo(theirs.fLocalMatrix));
}
static const GrGeometryProcessor* CreateGP(const Geometry& geo,
const GrXPOverridesForBatch& overrides) {
const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, overrides.readsCoverage(),
geo.fHasLocalRect,
geo.fHasLocalMatrix ? &geo.fLocalMatrix :
nullptr);
SkASSERT(geo.fHasLocalRect ?
gp->getVertexStride() == sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr) :
gp->getVertexStride() == sizeof(GrDefaultGeoProcFactory::PositionColorAttr));
return gp;
}
static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry& geo,
const GrXPOverridesForBatch& overrides) {
if (geo.fHasLocalRect) {
GrQuad quad(geo.fLocalRect);
tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect, &quad);
} else {
tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect, nullptr);
}
}
};
typedef GrTInstanceBatch<NonAAFillRectBatchImp> NonAAFillRectBatchSimple;
typedef GrTInstanceBatch<NonAAFillRectBatchPerspectiveImp> NonAAFillRectBatchPerspective;
inline static void append_to_batch(NonAAFillRectBatchSimple* batch, GrColor color,
const SkMatrix& viewMatrix, const SkRect& rect,
const SkRect* localRect, const SkMatrix* localMatrix) {
SkASSERT(!viewMatrix.hasPerspective() && (!localMatrix || !localMatrix->hasPerspective()));
NonAAFillRectBatchSimple::Geometry& geo = batch->geoData()->push_back();
geo.fColor = color;
geo.fViewMatrix = viewMatrix;
geo.fRect = rect;
if (localRect && localMatrix) {
geo.fLocalQuad.setFromMappedRect(*localRect, *localMatrix);
} else if (localRect) {
geo.fLocalQuad.set(*localRect);
} else if (localMatrix) {
geo.fLocalQuad.setFromMappedRect(rect, *localMatrix);
} else {
geo.fLocalQuad.set(rect);
}
}
inline static void append_to_batch(NonAAFillRectBatchPerspective* batch, GrColor color,
const SkMatrix& viewMatrix, const SkRect& rect,
const SkRect* localRect, const SkMatrix* localMatrix) {
SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->push_back();
geo.fColor = color;
geo.fViewMatrix = viewMatrix;
geo.fRect = rect;
geo.fHasLocalRect = SkToBool(localRect);
geo.fHasLocalMatrix = SkToBool(localMatrix);
if (localMatrix) {
geo.fLocalMatrix = *localMatrix;
}
if (localRect) {
geo.fLocalRect = *localRect;
}
}
namespace GrNonAAFillRectBatch {
GrDrawBatch* Create(GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect* localRect,
const SkMatrix* localMatrix) {
NonAAFillRectBatchSimple* batch = NonAAFillRectBatchSimple::Create();
append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
batch->init();
return batch;
}
GrDrawBatch* CreateWithPerspective(GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect* localRect,
const SkMatrix* localMatrix) {
NonAAFillRectBatchPerspective* batch = NonAAFillRectBatchPerspective::Create();
append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
batch->init();
return batch;
}
bool Append(GrBatch* origBatch,
GrColor color,
const SkMatrix& viewMatrix,
const SkRect& rect,
const SkRect* localRect,
const SkMatrix* localMatrix) {
bool usePerspective = viewMatrix.hasPerspective() ||
(localMatrix && localMatrix->hasPerspective());
if (usePerspective && origBatch->classID() != NonAAFillRectBatchPerspective::ClassID()) {
return false;
}
if (!usePerspective) {
NonAAFillRectBatchSimple* batch = origBatch->cast<NonAAFillRectBatchSimple>();
append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
batch->updateBoundsAfterAppend();
} else {
NonAAFillRectBatchPerspective* batch = origBatch->cast<NonAAFillRectBatchPerspective>();
const NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->back();
if (!geo.fViewMatrix.cheapEqualTo(viewMatrix) ||
geo.fHasLocalRect != SkToBool(localRect) ||
geo.fHasLocalMatrix != SkToBool(localMatrix) ||
(geo.fHasLocalMatrix && !geo.fLocalMatrix.cheapEqualTo(*localMatrix))) {
return false;
}
append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix);
batch->updateBoundsAfterAppend();
}
return true;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GR_TEST_UTILS
#include "GrBatchTest.h"
DRAW_BATCH_TEST_DEFINE(RectBatch) {
GrColor color = GrRandomColor(random);
SkRect rect = GrTest::TestRect(random);
SkRect localRect = GrTest::TestRect(random);
SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
SkMatrix localMatrix = GrTest::TestMatrix(random);
bool hasLocalRect = random->nextBool();
bool hasLocalMatrix = random->nextBool();
return GrNonAAFillRectBatch::Create(color, viewMatrix, rect,
hasLocalRect ? &localRect : nullptr,
hasLocalMatrix ? &localMatrix : nullptr);
}
#endif