Reland "Use specialized quad lists in rectangle ops"
This is a reland of 5820b0c3f3
It is updated in patchset 2 to clean up pointers passed into memcpy, and to
optimize the bounds calculation in GrPerspQuad. This should fix a performance
regression caused by the move away from caching 1/w. The Sk4f::invert() does not
always preserve 1/1 == 1, which led to bounds slightly outside of clips and
thus forced Skia to keep the scissor test enabled. The fix also restores the
optimization of skipping the 1/w division when the quad is known to be 2D.
Original change's description:
> Use specialized quad lists in rectangle ops
>
> Hopefully reduces memory footprint of GrFillRectOp and GrTextureOp
>
> The original rect code (GrAAFillRectOp) stored 2 SkMatrices (18 floats), 2
> SkRects (8 floats) an SkPMColor4f (4 floats) and a flag (1 int) for a total
> of 124 bytes per quad that was stored in the op.
>
> The first pass at the rectangle consolidation switched to storing device and
> local quads as GrPerspQuads (32 floats), an SkPMColor4f (4 floats) and a flag
> (1 int) for a total of 148 bytes per quad. After landing, several memory
> regressions appeared in Chrome and our perf monitor.
>
> Several intertwined approaches are taken here. First, GrPerspQuad no longer
> caches 1/w, which makes a quad 12 floats instead of 16. Second, a specialized
> list type is defined that allows storing the x, y, and extra metadata together
> for quads, but keeps the w components separate. When the quad type isn't
> perspective, w is not stored at all since it is implicitly 1 and can be
> reconstituted at tessellation time. This brings the total per quad to either
> 84 or 116 bytes, depending on if the op list needs perspective information.
>
> Bug: chromium:915025
> Bug: chromium:917242
> Change-Id: If37ee122847b0c32604bb45dc2a1326b544f9cf6
> Reviewed-on: https://skia-review.googlesource.com/c/180644
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
> Reviewed-by: Robert Phillips <robertphillips@google.com>
Bug: chromium:915025, chromium:917242
Change-Id: I98a1bf83fd7d393604823d567c57d7e06fad5e55
Reviewed-on: https://skia-review.googlesource.com/c/182203
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
parent
a870b46c79
commit
c96fc37f23
@ -101,6 +101,7 @@ tests_sources = [
|
||||
"$_tests/GrOpListFlushTest.cpp",
|
||||
"$_tests/GrPipelineDynamicStateTest.cpp",
|
||||
"$_tests/GrPorterDuffTest.cpp",
|
||||
"$_tests/GrQuadListTest.cpp",
|
||||
"$_tests/GrShapeTest.cpp",
|
||||
"$_tests/GrSKSLPrettyPrintTest.cpp",
|
||||
"$_tests/GrSurfaceTest.cpp",
|
||||
|
@ -202,7 +202,6 @@ GrPerspQuad::GrPerspQuad(const SkRect& rect, const SkMatrix& m) {
|
||||
SkNx_shuffle<0, 0, 2, 2>(r).store(fX);
|
||||
SkNx_shuffle<1, 3, 1, 3>(r).store(fY);
|
||||
fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
|
||||
fIW[0] = fIW[1] = fIW[2] = fIW[3] = 1.f;
|
||||
} else {
|
||||
Sk4f rx(rect.fLeft, rect.fLeft, rect.fRight, rect.fRight);
|
||||
Sk4f ry(rect.fTop, rect.fBottom, rect.fTop, rect.fBottom);
|
||||
@ -220,14 +219,19 @@ GrPerspQuad::GrPerspQuad(const SkRect& rect, const SkMatrix& m) {
|
||||
Sk4f w2(m.get(SkMatrix::kMPersp2));
|
||||
auto w = SkNx_fma(w0, rx, SkNx_fma(w1, ry, w2));
|
||||
w.store(fW);
|
||||
w.invert().store(fIW);
|
||||
} else {
|
||||
fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
|
||||
fIW[0] = fIW[1] = fIW[2] = fIW[3] = 1.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private constructor used by GrQuadList to quickly fill in a quad's values from the channel arrays
|
||||
GrPerspQuad::GrPerspQuad(const float* xs, const float* ys, const float* ws) {
|
||||
memcpy(fX, xs, 4 * sizeof(float));
|
||||
memcpy(fY, ys, 4 * sizeof(float));
|
||||
memcpy(fW, ws, 4 * sizeof(float));
|
||||
}
|
||||
|
||||
bool GrPerspQuad::aaHasEffectOnRect() const {
|
||||
SkASSERT(this->quadType() == GrQuadType::kRect);
|
||||
// If rect, ws must all be 1s so no need to divide
|
||||
|
211
src/gpu/GrQuad.h
211
src/gpu/GrQuad.h
@ -12,6 +12,7 @@
|
||||
#include "SkNx.h"
|
||||
#include "SkPoint.h"
|
||||
#include "SkPoint3.h"
|
||||
#include "SkTArray.h"
|
||||
|
||||
enum class GrAAType : unsigned;
|
||||
enum class GrQuadAAFlags;
|
||||
@ -90,6 +91,9 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
friend class GrQuadListBase;
|
||||
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
};
|
||||
@ -104,21 +108,29 @@ public:
|
||||
|
||||
SkPoint3 point(int i) const { return {fX[i], fY[i], fW[i]}; }
|
||||
|
||||
SkRect bounds() const {
|
||||
auto x = this->x4f() * this->iw4f();
|
||||
auto y = this->y4f() * this->iw4f();
|
||||
SkRect bounds(GrQuadType type) const {
|
||||
SkASSERT(this->quadType() <= type);
|
||||
|
||||
Sk4f x = this->x4f();
|
||||
Sk4f y = this->y4f();
|
||||
if (type == GrQuadType::kPerspective) {
|
||||
Sk4f iw = this->iw4f();
|
||||
x *= iw;
|
||||
y *= iw;
|
||||
}
|
||||
|
||||
return {x.min(), y.min(), x.max(), y.max()};
|
||||
}
|
||||
|
||||
float x(int i) const { return fX[i]; }
|
||||
float y(int i) const { return fY[i]; }
|
||||
float w(int i) const { return fW[i]; }
|
||||
float iw(int i) const { return fIW[i]; }
|
||||
float iw(int i) const { return sk_ieee_float_divide(1.f, fW[i]); }
|
||||
|
||||
Sk4f x4f() const { return Sk4f::Load(fX); }
|
||||
Sk4f y4f() const { return Sk4f::Load(fY); }
|
||||
Sk4f w4f() const { return Sk4f::Load(fW); }
|
||||
Sk4f iw4f() const { return Sk4f::Load(fIW); }
|
||||
Sk4f iw4f() const { return this->w4f().invert(); }
|
||||
|
||||
bool hasPerspective() const { return (w4f() != Sk4f(1.f)).anyTrue(); }
|
||||
|
||||
@ -130,10 +142,197 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
friend class GrQuadListBase;
|
||||
|
||||
// Copy 4 values from each of the arrays into the quad's components
|
||||
GrPerspQuad(const float xs[4], const float ys[4], const float ws[4]);
|
||||
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
float fW[4];
|
||||
float fIW[4]; // 1/w
|
||||
};
|
||||
|
||||
// Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
|
||||
// issues related to specializing member types.
|
||||
template<typename T>
|
||||
struct QuadData {
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
T fMetadata;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct QuadData<void> {
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
};
|
||||
|
||||
// A dynamic list of (possibly) perspective quads that tracks the most general quad type of all
|
||||
// added quads. It avoids storing the 3rd component if the quad type never becomes perspective.
|
||||
// Use GrQuadList subclass when only storing quads. Use GrTQuadList subclass when storing quads
|
||||
// and per-quad templated metadata (such as color or domain).
|
||||
template<typename T>
|
||||
class GrQuadListBase {
|
||||
public:
|
||||
|
||||
int count() const { return fXYs.count(); }
|
||||
|
||||
GrQuadType quadType() const { return fType; }
|
||||
|
||||
void reserve(int count, GrQuadType forType) {
|
||||
fXYs.reserve(count);
|
||||
if (forType == GrQuadType::kPerspective || fType == GrQuadType::kPerspective) {
|
||||
fWs.reserve(4 * count);
|
||||
}
|
||||
}
|
||||
|
||||
GrPerspQuad operator[] (int i) const {
|
||||
SkASSERT(i < this->count());
|
||||
SkASSERT(i >= 0);
|
||||
|
||||
const QuadData<T>& item = fXYs[i];
|
||||
if (fType == GrQuadType::kPerspective) {
|
||||
// Read the explicit ws
|
||||
return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i);
|
||||
} else {
|
||||
// Ws are implicitly 1s.
|
||||
static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
|
||||
return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs);
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses expose push_back(const GrQuad|GrPerspQuad&, GrQuadType, [const T&]), where
|
||||
// the metadata argument is only present in GrTQuadList's push_back definition.
|
||||
|
||||
protected:
|
||||
GrQuadListBase() : fType(GrQuadType::kRect) {}
|
||||
|
||||
void concatImpl(const GrQuadListBase<T>& that) {
|
||||
this->upgradeType(that.fType);
|
||||
fXYs.push_back_n(that.fXYs.count(), that.fXYs.begin());
|
||||
if (fType == GrQuadType::kPerspective) {
|
||||
if (that.fType == GrQuadType::kPerspective) {
|
||||
// Copy the other's ws into the end of this list's data
|
||||
fWs.push_back_n(that.fWs.count(), that.fWs.begin());
|
||||
} else {
|
||||
// This list stores ws but the appended list had implicit 1s, so add explicit 1s to
|
||||
// fill out the total list
|
||||
fWs.push_back_n(4 * that.count(), 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the added item data so that its metadata can be initialized if T is not void
|
||||
QuadData<T>& pushBackImpl(const GrQuad& quad, GrQuadType type) {
|
||||
SkASSERT(quad.quadType() <= type);
|
||||
|
||||
this->upgradeType(type);
|
||||
QuadData<T>& item = fXYs.push_back();
|
||||
memcpy(item.fX, quad.fX, 4 * sizeof(float));
|
||||
memcpy(item.fY, quad.fY, 4 * sizeof(float));
|
||||
if (fType == GrQuadType::kPerspective) {
|
||||
fWs.push_back_n(4, 1.f);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
QuadData<T>& pushBackImpl(const GrPerspQuad& quad, GrQuadType type) {
|
||||
SkASSERT(quad.quadType() <= type);
|
||||
|
||||
this->upgradeType(type);
|
||||
QuadData<T>& item = fXYs.push_back();
|
||||
memcpy(item.fX, quad.fX, 4 * sizeof(float));
|
||||
memcpy(item.fY, quad.fY, 4 * sizeof(float));
|
||||
if (fType == GrQuadType::kPerspective) {
|
||||
fWs.push_back_n(4, quad.fW);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
const QuadData<T>& item(int i) const {
|
||||
return fXYs[i];
|
||||
}
|
||||
|
||||
QuadData<T>& item(int i) {
|
||||
return fXYs[i];
|
||||
}
|
||||
|
||||
private:
|
||||
void upgradeType(GrQuadType type) {
|
||||
// Possibly upgrade the overall type tracked by the list
|
||||
if (type > fType) {
|
||||
fType = type;
|
||||
if (type == GrQuadType::kPerspective) {
|
||||
// All existing quads were 2D, so the ws array just needs to be filled with 1s
|
||||
fWs.push_back_n(4 * this->count(), 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interleaves xs, ys, and per-quad metadata so that all data for a single quad is together
|
||||
// (barring ws, which can be dropped entirely if the quad type allows it).
|
||||
SkSTArray<1, QuadData<T>, true> fXYs;
|
||||
// The w channel is kept separate so that it can remain empty when only dealing with 2D quads.
|
||||
SkTArray<float, true> fWs;
|
||||
|
||||
GrQuadType fType;
|
||||
};
|
||||
|
||||
// This list only stores the quad data itself.
|
||||
class GrQuadList : public GrQuadListBase<void> {
|
||||
public:
|
||||
GrQuadList() : INHERITED() {}
|
||||
|
||||
void concat(const GrQuadList& that) {
|
||||
this->concatImpl(that);
|
||||
}
|
||||
|
||||
void push_back(const GrQuad& quad, GrQuadType type) {
|
||||
this->pushBackImpl(quad, type);
|
||||
}
|
||||
|
||||
void push_back(const GrPerspQuad& quad, GrQuadType type) {
|
||||
this->pushBackImpl(quad, type);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GrQuadListBase<void> INHERITED;
|
||||
};
|
||||
|
||||
// This variant of the list allows simple metadata to be stored per quad as well, such as color
|
||||
// or texture domain.
|
||||
template<typename T>
|
||||
class GrTQuadList : public GrQuadListBase<T> {
|
||||
public:
|
||||
GrTQuadList() : INHERITED() {}
|
||||
|
||||
void concat(const GrTQuadList<T>& that) {
|
||||
this->concatImpl(that);
|
||||
}
|
||||
|
||||
// Adding to the list requires metadata
|
||||
void push_back(const GrQuad& quad, GrQuadType type, T&& metadata) {
|
||||
QuadData<T>& item = this->pushBackImpl(quad, type);
|
||||
item.fMetadata = std::move(metadata);
|
||||
}
|
||||
|
||||
void push_back(const GrPerspQuad& quad, GrQuadType type, T&& metadata) {
|
||||
QuadData<T>& item = this->pushBackImpl(quad, type);
|
||||
item.fMetadata = std::move(metadata);
|
||||
}
|
||||
|
||||
// And provide access to the metadata per quad
|
||||
const T& metadata(int i) const {
|
||||
return this->item(i).fMetadata;
|
||||
}
|
||||
|
||||
T& metadata(int i) {
|
||||
return this->item(i).fMetadata;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GrQuadListBase<T> INHERITED;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -24,54 +24,32 @@ namespace {
|
||||
using VertexSpec = GrQuadPerEdgeAA::VertexSpec;
|
||||
using ColorType = GrQuadPerEdgeAA::ColorType;
|
||||
|
||||
// NOTE: This info structure is intentionally modeled after GrTextureOps' Quad so that they can
|
||||
// more easily be integrated together in the future.
|
||||
class TransformedQuad {
|
||||
public:
|
||||
TransformedQuad(const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad,
|
||||
const SkPMColor4f& color, GrQuadAAFlags aaFlags)
|
||||
: fDeviceQuad(deviceQuad)
|
||||
, fLocalQuad(localQuad)
|
||||
, fColor(color)
|
||||
, fAAFlags(aaFlags) {}
|
||||
|
||||
const GrPerspQuad& deviceQuad() const { return fDeviceQuad; }
|
||||
const GrPerspQuad& localQuad() const { return fLocalQuad; }
|
||||
const SkPMColor4f& color() const { return fColor; }
|
||||
GrQuadAAFlags aaFlags() const { return fAAFlags; }
|
||||
|
||||
void setColor(const SkPMColor4f& color) { fColor = color; }
|
||||
|
||||
SkString dumpInfo(int index) const {
|
||||
SkString str;
|
||||
str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f], Edge AA: l%u_t%u_r%u_b%u, \n"
|
||||
" device quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
|
||||
"(%.2f, %.2f, %.2f)],\n"
|
||||
" local quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
|
||||
"(%.2f, %.2f, %.2f)]\n",
|
||||
index, fColor.fR, fColor.fG, fColor.fB, fColor.fA,
|
||||
(uint32_t) (fAAFlags & GrQuadAAFlags::kLeft),
|
||||
(uint32_t) (fAAFlags & GrQuadAAFlags::kTop),
|
||||
(uint32_t) (fAAFlags & GrQuadAAFlags::kRight),
|
||||
(uint32_t) (fAAFlags & GrQuadAAFlags::kBottom),
|
||||
fDeviceQuad.x(0), fDeviceQuad.y(0), fDeviceQuad.w(0),
|
||||
fDeviceQuad.x(1), fDeviceQuad.y(1), fDeviceQuad.w(1),
|
||||
fDeviceQuad.x(2), fDeviceQuad.y(2), fDeviceQuad.w(2),
|
||||
fDeviceQuad.x(3), fDeviceQuad.y(3), fDeviceQuad.w(3),
|
||||
fLocalQuad.x(0), fLocalQuad.y(0), fLocalQuad.w(0),
|
||||
fLocalQuad.x(1), fLocalQuad.y(1), fLocalQuad.w(1),
|
||||
fLocalQuad.x(2), fLocalQuad.y(2), fLocalQuad.w(2),
|
||||
fLocalQuad.x(3), fLocalQuad.y(3), fLocalQuad.w(3));
|
||||
return str;
|
||||
}
|
||||
private:
|
||||
// NOTE: The TransformedQuad does not store the types for device and local. The owning op tracks
|
||||
// the most general type for device and local across all of its merged quads.
|
||||
GrPerspQuad fDeviceQuad; // In device space, allowing rects to be combined across view matrices
|
||||
GrPerspQuad fLocalQuad; // Original rect transformed by its local matrix
|
||||
SkPMColor4f fColor;
|
||||
GrQuadAAFlags fAAFlags;
|
||||
};
|
||||
#ifdef SK_DEBUG
|
||||
static SkString dump_quad_info(int index, const GrPerspQuad& deviceQuad,
|
||||
const GrPerspQuad& localQuad, const SkPMColor4f& color,
|
||||
GrQuadAAFlags aaFlags) {
|
||||
SkString str;
|
||||
str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f], Edge AA: l%u_t%u_r%u_b%u, \n"
|
||||
" device quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
|
||||
"(%.2f, %.2f, %.2f)],\n"
|
||||
" local quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
|
||||
"(%.2f, %.2f, %.2f)]\n",
|
||||
index, color.fR, color.fG, color.fB, color.fA,
|
||||
(uint32_t) (aaFlags & GrQuadAAFlags::kLeft),
|
||||
(uint32_t) (aaFlags & GrQuadAAFlags::kTop),
|
||||
(uint32_t) (aaFlags & GrQuadAAFlags::kRight),
|
||||
(uint32_t) (aaFlags & GrQuadAAFlags::kBottom),
|
||||
deviceQuad.x(0), deviceQuad.y(0), deviceQuad.w(0),
|
||||
deviceQuad.x(1), deviceQuad.y(1), deviceQuad.w(1),
|
||||
deviceQuad.x(2), deviceQuad.y(2), deviceQuad.w(2),
|
||||
deviceQuad.x(3), deviceQuad.y(3), deviceQuad.w(3),
|
||||
localQuad.x(0), localQuad.y(0), localQuad.w(0),
|
||||
localQuad.x(1), localQuad.y(1), localQuad.w(1),
|
||||
localQuad.x(2), localQuad.y(2), localQuad.w(2),
|
||||
localQuad.x(3), localQuad.y(3), localQuad.w(3));
|
||||
return str;
|
||||
}
|
||||
#endif
|
||||
|
||||
class FillRectOp final : public GrMeshDrawOp {
|
||||
private:
|
||||
@ -113,9 +91,7 @@ public:
|
||||
const GrPerspQuad& deviceQuad, GrQuadType deviceQuadType,
|
||||
const GrPerspQuad& localQuad, GrQuadType localQuadType)
|
||||
: INHERITED(ClassID())
|
||||
, fHelper(args, aaType, stencil)
|
||||
, fDeviceQuadType(static_cast<unsigned>(deviceQuadType))
|
||||
, fLocalQuadType(static_cast<unsigned>(localQuadType)) {
|
||||
, fHelper(args, aaType, stencil) {
|
||||
if (constBlendColor) {
|
||||
// The GrPaint is compatible with clearing, and the constant blend color overrides the
|
||||
// paint color (although in most cases they are probably the same)
|
||||
@ -133,9 +109,10 @@ public:
|
||||
|
||||
// The color stored with the quad is the clear color if a scissor-clear is decided upon
|
||||
// when executing the op.
|
||||
fQuads.emplace_back(deviceQuad, localQuad, paintColor, edgeFlags);
|
||||
this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
|
||||
IsZeroArea::kNo);
|
||||
fDeviceQuads.push_back(deviceQuad, deviceQuadType, { paintColor, edgeFlags });
|
||||
fLocalQuads.push_back(localQuad, localQuadType);
|
||||
this->setBounds(deviceQuad.bounds(deviceQuadType),
|
||||
HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
|
||||
}
|
||||
|
||||
const char* name() const override { return "FillRectOp"; }
|
||||
@ -147,14 +124,16 @@ public:
|
||||
#ifdef SK_DEBUG
|
||||
SkString dumpInfo() const override {
|
||||
SkString str;
|
||||
str.appendf("# draws: %d\n", fQuads.count());
|
||||
str.appendf("Clear compatible: %u\n", static_cast<bool>(fClearCompatible));
|
||||
str.appendf("# draws: %u\n", this->quadCount());
|
||||
str.appendf("Device quad type: %u, local quad type: %u\n",
|
||||
fDeviceQuadType, fLocalQuadType);
|
||||
(uint32_t) fDeviceQuads.quadType(), (uint32_t) fLocalQuads.quadType());
|
||||
str += fHelper.dumpInfo();
|
||||
for (int i = 0; i < fQuads.count(); i++) {
|
||||
str += fQuads[i].dumpInfo(i);
|
||||
|
||||
GrPerspQuad device, local;
|
||||
for (int i = 0; i < this->quadCount(); i++) {
|
||||
device = fDeviceQuads[i];
|
||||
const ColorAndAA& info = fDeviceQuads.metadata(i);
|
||||
local = fLocalQuads[i];
|
||||
str += dump_quad_info(i, device, local, info.fColor, info.fAAFlags);
|
||||
}
|
||||
str += INHERITED::dumpInfo();
|
||||
return str;
|
||||
@ -163,11 +142,12 @@ public:
|
||||
|
||||
RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
|
||||
// Initialize aggregate color analysis with the first quad's color (which always exists)
|
||||
SkASSERT(fQuads.count() > 0);
|
||||
GrProcessorAnalysisColor quadColors(fQuads[0].color());
|
||||
SkASSERT(this->quadCount() > 0);
|
||||
GrProcessorAnalysisColor quadColors(fDeviceQuads.metadata(0).fColor);
|
||||
// Then combine the colors of any additional quads (e.g. from MakeSet)
|
||||
for (int i = 1; i < fQuads.count(); ++i) {
|
||||
quadColors = GrProcessorAnalysisColor::Combine(quadColors, fQuads[i].color());
|
||||
for (int i = 1; i < this->quadCount(); ++i) {
|
||||
quadColors = GrProcessorAnalysisColor::Combine(quadColors,
|
||||
fDeviceQuads.metadata(i).fColor);
|
||||
if (quadColors.isUnknown()) {
|
||||
// No point in accumulating additional starting colors, combining cannot make it
|
||||
// less unknown.
|
||||
@ -185,8 +165,8 @@ public:
|
||||
// to the same color (even if they started out with different colors).
|
||||
SkPMColor4f colorOverride;
|
||||
if (quadColors.isConstant(&colorOverride)) {
|
||||
for (int i = 0; i < fQuads.count(); ++i) {
|
||||
fQuads[i].setColor(colorOverride);
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
fDeviceQuads.metadata(i).fColor = colorOverride;
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,21 +183,20 @@ public:
|
||||
|
||||
private:
|
||||
// For GrFillRectOp::MakeSet's use of addQuad
|
||||
// FIXME(reviewer): better to just make addQuad public?
|
||||
friend std::unique_ptr<GrDrawOp> GrFillRectOp::MakeSet(GrContext* context, GrPaint&& paint,
|
||||
GrAAType aaType, const SkMatrix& viewMatrix,
|
||||
const GrRenderTargetContext::QuadSetEntry quads[], int quadCount,
|
||||
const GrUserStencilSettings* stencilSettings);
|
||||
|
||||
void onPrepareDraws(Target* target) override {
|
||||
void onPrepareDraws(Target* target) override {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
using Domain = GrQuadPerEdgeAA::Domain;
|
||||
static constexpr SkRect kEmptyDomain = SkRect::MakeEmpty();
|
||||
|
||||
VertexSpec vertexSpec(this->deviceQuadType(),
|
||||
VertexSpec vertexSpec(fDeviceQuads.quadType(),
|
||||
fWideColor ? ColorType::kHalf : ColorType::kByte,
|
||||
this->localQuadType(), fHelper.usesLocalCoords(), Domain::kNo,
|
||||
fLocalQuads.quadType(), fHelper.usesLocalCoords(), Domain::kNo,
|
||||
fHelper.aaType(), fHelper.compatibleWithAlphaAsCoverage());
|
||||
|
||||
sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeProcessor(vertexSpec);
|
||||
@ -228,7 +207,7 @@ private:
|
||||
|
||||
// Fill the allocated vertex data
|
||||
void* vdata = target->makeVertexSpace(
|
||||
vertexSize, fQuads.count() * vertexSpec.verticesPerQuad(),
|
||||
vertexSize, this->quadCount() * vertexSpec.verticesPerQuad(),
|
||||
&vbuffer, &vertexOffsetInBuffer);
|
||||
if (!vdata) {
|
||||
SkDebugf("Could not allocate vertices\n");
|
||||
@ -237,15 +216,18 @@ private:
|
||||
|
||||
// vertices pointer advances through vdata based on Tessellate's return value
|
||||
void* vertices = vdata;
|
||||
for (int i = 0; i < fQuads.count(); ++i) {
|
||||
const auto& q = fQuads[i];
|
||||
vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, q.deviceQuad(), q.color(),
|
||||
q.localQuad(), kEmptyDomain, q.aaFlags());
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
const GrPerspQuad& device = fDeviceQuads[i];
|
||||
const ColorAndAA& info = fDeviceQuads.metadata(i);
|
||||
const GrPerspQuad& local = fLocalQuads[i];
|
||||
|
||||
vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, device, info.fColor, local,
|
||||
kEmptyDomain, info.fAAFlags);
|
||||
}
|
||||
|
||||
// Configure the mesh for the vertex data
|
||||
GrMesh* mesh = target->allocMeshes(1);
|
||||
if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, fQuads.count())) {
|
||||
if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, this->quadCount())) {
|
||||
SkDebugf("Could not allocate indices\n");
|
||||
return;
|
||||
}
|
||||
@ -261,13 +243,13 @@ private:
|
||||
|
||||
if ((fHelper.aaType() == GrAAType::kCoverage ||
|
||||
that->fHelper.aaType() == GrAAType::kCoverage) &&
|
||||
fQuads.count() + that->fQuads.count() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) {
|
||||
this->quadCount() + that->quadCount() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) {
|
||||
// This limit on batch size seems to help on Adreno devices
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
|
||||
// Unlike most users of the draw op helper, this op can merge none-aa and coverage-aa
|
||||
// draw ops together, so pass true as the last argument.
|
||||
// Unlike most users of the draw op helper, this op can merge none-aa and coverage-aa draw
|
||||
// ops together, so pass true as the last argument.
|
||||
if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds(), true)) {
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
@ -275,13 +257,6 @@ private:
|
||||
// If the processor sets are compatible, the two ops are always compatible; it just needs
|
||||
// to adjust the state of the op to be the more general quad and aa types of the two ops.
|
||||
|
||||
// The GrQuadType enum is ordered such that higher values are more general quad types
|
||||
if (that->fDeviceQuadType > fDeviceQuadType) {
|
||||
fDeviceQuadType = that->fDeviceQuadType;
|
||||
}
|
||||
if (that->fLocalQuadType > fLocalQuadType) {
|
||||
fLocalQuadType = that->fLocalQuadType;
|
||||
}
|
||||
fClearCompatible &= that->fClearCompatible;
|
||||
fWideColor |= that->fWideColor;
|
||||
|
||||
@ -292,7 +267,8 @@ private:
|
||||
fHelper.setAAType(GrAAType::kCoverage);
|
||||
}
|
||||
|
||||
fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
|
||||
fDeviceQuads.concat(that->fDeviceQuads);
|
||||
fLocalQuads.concat(that->fLocalQuads);
|
||||
return CombineResult::kMerged;
|
||||
}
|
||||
|
||||
@ -300,8 +276,10 @@ private:
|
||||
// But since it's avoiding the op list management, it must update the op's bounds. This is only
|
||||
// used with quad sets, which uses the same view matrix for each quad so this assumes that the
|
||||
// device quad type of the new quad is the same as the op's.
|
||||
void addQuad(TransformedQuad&& quad, GrQuadType localQuadType, GrAAType aaType) {
|
||||
SkASSERT(quad.deviceQuad().quadType() <= this->deviceQuadType());
|
||||
void addQuad(const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad,
|
||||
GrQuadType localQuadType, const SkPMColor4f& color, GrQuadAAFlags edgeAA,
|
||||
GrAAType aaType) {
|
||||
SkASSERT(deviceQuad.quadType() <= fDeviceQuads.quadType());
|
||||
|
||||
// The new quad's aa type should be the same as the first quad's or none, except when the
|
||||
// first quad's aa type was already downgraded to none, in which case the stored type must
|
||||
@ -316,33 +294,35 @@ private:
|
||||
// reset the op's accumulated aa type.
|
||||
}
|
||||
|
||||
// The new quad's local coordinates could differ
|
||||
if (localQuadType > this->localQuadType()) {
|
||||
fLocalQuadType = static_cast<unsigned>(localQuadType);
|
||||
}
|
||||
|
||||
// clear compatible won't need to be updated, since device quad type and paint is the same,
|
||||
// but this quad has a new color, so maybe update wide color
|
||||
fWideColor |= !SkPMColor4fFitsInBytes(quad.color());
|
||||
fWideColor |= !SkPMColor4fFitsInBytes(color);
|
||||
|
||||
// Update the bounds and add the quad to this op's storage
|
||||
SkRect newBounds = this->bounds();
|
||||
newBounds.joinPossiblyEmptyRect(quad.deviceQuad().bounds());
|
||||
newBounds.joinPossiblyEmptyRect(deviceQuad.bounds(fDeviceQuads.quadType()));
|
||||
this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
|
||||
IsZeroArea::kNo);
|
||||
fQuads.push_back(std::move(quad));
|
||||
fDeviceQuads.push_back(deviceQuad, fDeviceQuads.quadType(), { color, edgeAA });
|
||||
fLocalQuads.push_back(localQuad, localQuadType);
|
||||
}
|
||||
|
||||
GrQuadType deviceQuadType() const { return static_cast<GrQuadType>(fDeviceQuadType); }
|
||||
GrQuadType localQuadType() const { return static_cast<GrQuadType>(fLocalQuadType); }
|
||||
int quadCount() const {
|
||||
// Sanity check that the parallel arrays for quad properties all have the same size
|
||||
SkASSERT(fDeviceQuads.count() == fLocalQuads.count());
|
||||
return fDeviceQuads.count();
|
||||
}
|
||||
|
||||
struct ColorAndAA {
|
||||
SkPMColor4f fColor;
|
||||
GrQuadAAFlags fAAFlags;
|
||||
};
|
||||
|
||||
Helper fHelper;
|
||||
SkSTArray<1, TransformedQuad, true> fQuads;
|
||||
GrTQuadList<ColorAndAA> fDeviceQuads;
|
||||
// No metadata attached to the local quads
|
||||
GrQuadList fLocalQuads;
|
||||
|
||||
// While we always store full GrPerspQuads in memory, if the type is known to be simpler we can
|
||||
// optimize our geometry generation.
|
||||
unsigned fDeviceQuadType: 2;
|
||||
unsigned fLocalQuadType: 2;
|
||||
unsigned fWideColor: 1;
|
||||
|
||||
// True if fQuad produced by a rectangle-preserving view matrix, is pixel aligned or non-AA,
|
||||
@ -422,9 +402,9 @@ std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
|
||||
GrResolveAATypeForQuad(aaType, quads[i].fAAFlags, deviceQuad, deviceQuadType,
|
||||
&resolvedAA, &resolvedEdgeFlags);
|
||||
|
||||
fillRects->addQuad({ deviceQuad, GrPerspQuad(quads[i].fRect, quads[i].fLocalMatrix),
|
||||
quads[i].fColor, resolvedEdgeFlags },
|
||||
GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), resolvedAA);
|
||||
fillRects->addQuad(deviceQuad, GrPerspQuad(quads[i].fRect, quads[i].fLocalMatrix),
|
||||
GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), quads[i].fColor,
|
||||
resolvedEdgeFlags,resolvedAA);
|
||||
}
|
||||
|
||||
return op;
|
||||
|
@ -169,15 +169,16 @@ public:
|
||||
str.appendf("Proxy ID: %d, Filter: %d\n", fProxies[p].fProxy->uniqueID().asUInt(),
|
||||
static_cast<int>(fFilter));
|
||||
for (int i = 0; i < fProxies[p].fQuadCnt; ++i, ++q) {
|
||||
const Quad& quad = fQuads[q];
|
||||
GrPerspQuad quad = fQuads[q];
|
||||
const ColorDomainAndAA& info = fQuads.metadata(i);
|
||||
str.appendf(
|
||||
"%d: Color: 0x%08x, TexRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f] "
|
||||
"Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n",
|
||||
i, quad.color().toBytes_RGBA(), quad.srcRect().fLeft, quad.srcRect().fTop,
|
||||
quad.srcRect().fRight, quad.srcRect().fBottom, quad.quad().point(0).fX,
|
||||
quad.quad().point(0).fY, quad.quad().point(1).fX, quad.quad().point(1).fY,
|
||||
quad.quad().point(2).fX, quad.quad().point(2).fY, quad.quad().point(3).fX,
|
||||
quad.quad().point(3).fY);
|
||||
i, info.fColor.toBytes_RGBA(), info.fSrcRect.fLeft, info.fSrcRect.fTop,
|
||||
info.fSrcRect.fRight, info.fSrcRect.fBottom, quad.point(0).fX,
|
||||
quad.point(0).fY, quad.point(1).fX, quad.point(1).fY,
|
||||
quad.point(2).fX, quad.point(2).fY, quad.point(3).fX,
|
||||
quad.point(3).fY);
|
||||
}
|
||||
}
|
||||
str += INHERITED::dumpInfo();
|
||||
@ -221,7 +222,6 @@ private:
|
||||
GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags);
|
||||
fAAType = static_cast<unsigned>(aaType);
|
||||
|
||||
fQuadType = static_cast<unsigned>(quadType);
|
||||
// We expect our caller to have already caught this optimization.
|
||||
SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
|
||||
constraint == SkCanvas::kFast_SrcRectConstraint);
|
||||
@ -241,12 +241,15 @@ private:
|
||||
aaType != GrAAType::kCoverage) {
|
||||
constraint = SkCanvas::kFast_SrcRectConstraint;
|
||||
}
|
||||
const auto& draw = fQuads.emplace_back(srcRect, quad, aaFlags, constraint, color);
|
||||
|
||||
Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes
|
||||
: Domain::kNo;
|
||||
fQuads.push_back(quad, quadType, {color, srcRect, domain, aaFlags});
|
||||
fProxyCnt = 1;
|
||||
fProxies[0] = {proxy.release(), 1};
|
||||
auto bounds = quad.bounds();
|
||||
auto bounds = quad.bounds(quadType);
|
||||
this->setBounds(bounds, HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
|
||||
fDomain = static_cast<unsigned>(draw.domain());
|
||||
fDomain = static_cast<unsigned>(domain);
|
||||
fWideColor = !SkPMColor4fFitsInBytes(color);
|
||||
fCanSkipAllocatorGather =
|
||||
static_cast<unsigned>(fProxies[0].fProxy->canSkipResourceAllocator());
|
||||
@ -258,7 +261,6 @@ private:
|
||||
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
|
||||
, fFilter(static_cast<unsigned>(filter))
|
||||
, fFinalized(0) {
|
||||
fQuads.reserve(cnt);
|
||||
fProxyCnt = SkToUInt(cnt);
|
||||
SkRect bounds = SkRectPriv::MakeLargestInverted();
|
||||
GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
|
||||
@ -266,6 +268,8 @@ private:
|
||||
fCanSkipAllocatorGather = static_cast<unsigned>(true);
|
||||
// All dst rects are transformed by the same view matrix, so their quad types are identical
|
||||
GrQuadType quadType = GrQuadTypeForTransformedRect(viewMatrix);
|
||||
fQuads.reserve(cnt, quadType);
|
||||
|
||||
for (unsigned p = 0; p < fProxyCnt; ++p) {
|
||||
fProxies[p].fProxy = SkRef(set[p].fProxy.get());
|
||||
fProxies[p].fQuadCnt = 1;
|
||||
@ -275,7 +279,7 @@ private:
|
||||
fCanSkipAllocatorGather = static_cast<unsigned>(false);
|
||||
}
|
||||
auto quad = GrPerspQuad(set[p].fDstRect, viewMatrix);
|
||||
bounds.joinPossiblyEmptyRect(quad.bounds());
|
||||
bounds.joinPossiblyEmptyRect(quad.bounds(quadType));
|
||||
GrQuadAAFlags aaFlags;
|
||||
// Don't update the overall aaType, might be inappropriate for some of the quads
|
||||
GrAAType aaForQuad;
|
||||
@ -291,15 +295,13 @@ private:
|
||||
}
|
||||
float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);
|
||||
SkPMColor4f color{alpha, alpha, alpha, alpha};
|
||||
fQuads.emplace_back(set[p].fSrcRect, quad, aaFlags, SkCanvas::kFast_SrcRectConstraint,
|
||||
color);
|
||||
fQuads.push_back(quad, quadType, {color, set[p].fSrcRect, Domain::kNo, aaFlags});
|
||||
}
|
||||
fAAType = static_cast<unsigned>(overallAAType);
|
||||
if (!mustFilter) {
|
||||
fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
|
||||
}
|
||||
this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
|
||||
fQuadType = static_cast<unsigned>(quadType);
|
||||
fDomain = static_cast<unsigned>(false);
|
||||
fWideColor = static_cast<unsigned>(false);
|
||||
}
|
||||
@ -320,12 +322,14 @@ private:
|
||||
}
|
||||
|
||||
for (int i = start; i < start + cnt; ++i) {
|
||||
const auto q = fQuads[i];
|
||||
GrPerspQuad srcQuad = compute_src_quad(origin, q.srcRect(), iw, ih, h);
|
||||
const GrPerspQuad& device = fQuads[i];
|
||||
const ColorDomainAndAA& info = fQuads.metadata(i);
|
||||
|
||||
GrPerspQuad srcQuad = compute_src_quad(origin, info.fSrcRect, iw, ih, h);
|
||||
SkRect domain =
|
||||
compute_domain(q.domain(), this->filter(), origin, q.srcRect(), iw, ih, h);
|
||||
v = GrQuadPerEdgeAA::Tessellate(v, spec, q.quad(), q.color(), srcQuad, domain,
|
||||
q.aaFlags());
|
||||
compute_domain(info.domain(), this->filter(), origin, info.fSrcRect, iw, ih, h);
|
||||
v = GrQuadPerEdgeAA::Tessellate(v, spec, device, info.fColor, srcQuad, domain,
|
||||
info.aaFlags());
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,8 +344,8 @@ private:
|
||||
auto config = fProxies[0].fProxy->config();
|
||||
GrAAType aaType = this->aaType();
|
||||
for (const auto& op : ChainRange<TextureOp>(this)) {
|
||||
if (op.quadType() > quadType) {
|
||||
quadType = op.quadType();
|
||||
if (op.fQuads.quadType() > quadType) {
|
||||
quadType = op.fQuads.quadType();
|
||||
}
|
||||
if (op.fDomain) {
|
||||
domain = Domain::kYes;
|
||||
@ -485,10 +489,7 @@ private:
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
fProxies[0].fQuadCnt += that->fQuads.count();
|
||||
fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
|
||||
if (that->fQuadType > fQuadType) {
|
||||
fQuadType = that->fQuadType;
|
||||
}
|
||||
fQuads.concat(that->fQuads);
|
||||
fDomain |= that->fDomain;
|
||||
fWideColor |= that->fWideColor;
|
||||
if (upgradeToCoverageAAOnMerge) {
|
||||
@ -499,47 +500,44 @@ private:
|
||||
|
||||
GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
|
||||
GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
|
||||
GrQuadType quadType() const { return static_cast<GrQuadType>(fQuadType); }
|
||||
|
||||
class Quad {
|
||||
public:
|
||||
Quad(const SkRect& srcRect, const GrPerspQuad& quad, GrQuadAAFlags aaFlags,
|
||||
SkCanvas::SrcRectConstraint constraint, const SkPMColor4f& color)
|
||||
: fSrcRect(srcRect)
|
||||
, fQuad(quad)
|
||||
, fColor(color)
|
||||
, fHasDomain(constraint == SkCanvas::kStrict_SrcRectConstraint)
|
||||
struct ColorDomainAndAA {
|
||||
// Special constructor to convert enums into the packed bits, which should not delete
|
||||
// the implicit move constructor (but it does require us to declare an empty ctor for
|
||||
// use with the GrTQuadList).
|
||||
ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect,
|
||||
Domain hasDomain, GrQuadAAFlags aaFlags)
|
||||
: fColor(color)
|
||||
, fSrcRect(srcRect)
|
||||
, fHasDomain(static_cast<unsigned>(hasDomain))
|
||||
, fAAFlags(static_cast<unsigned>(aaFlags)) {
|
||||
SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain));
|
||||
SkASSERT(fAAFlags == static_cast<unsigned>(aaFlags));
|
||||
}
|
||||
const GrPerspQuad& quad() const { return fQuad; }
|
||||
const SkRect& srcRect() const { return fSrcRect; }
|
||||
SkPMColor4f color() const { return fColor; }
|
||||
Domain domain() const { return Domain(fHasDomain); }
|
||||
GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
|
||||
ColorDomainAndAA() = default;
|
||||
|
||||
private:
|
||||
SkRect fSrcRect;
|
||||
GrPerspQuad fQuad;
|
||||
SkPMColor4f fColor;
|
||||
SkRect fSrcRect;
|
||||
unsigned fHasDomain : 1;
|
||||
unsigned fAAFlags : 4;
|
||||
|
||||
Domain domain() const { return Domain(fHasDomain); }
|
||||
GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
|
||||
};
|
||||
struct Proxy {
|
||||
GrTextureProxy* fProxy;
|
||||
int fQuadCnt;
|
||||
};
|
||||
SkSTArray<1, Quad, true> fQuads;
|
||||
GrTQuadList<ColorDomainAndAA> fQuads;
|
||||
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
|
||||
unsigned fFilter : 2;
|
||||
unsigned fAAType : 2;
|
||||
unsigned fQuadType : 2; // Device quad, src quad is always trivial
|
||||
unsigned fDomain : 1;
|
||||
unsigned fWideColor : 1;
|
||||
// Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
|
||||
unsigned fFinalized : 1;
|
||||
unsigned fCanSkipAllocatorGather : 1;
|
||||
unsigned fProxyCnt : 32 - 10;
|
||||
unsigned fProxyCnt : 32 - 8;
|
||||
Proxy fProxies[1];
|
||||
|
||||
static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits");
|
||||
|
282
tests/GrQuadListTest.cpp
Normal file
282
tests/GrQuadListTest.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Test.h"
|
||||
|
||||
#include "GrQuad.h"
|
||||
|
||||
#define ASSERT(cond) REPORTER_ASSERT(r, cond)
|
||||
#define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
|
||||
#define TEST(name) DEF_TEST(GrQuadList##name, r)
|
||||
|
||||
struct TestData {
|
||||
int fItem1;
|
||||
float fItem2;
|
||||
};
|
||||
|
||||
// Simple factories to make placeholder quads used in the tests. The 2D quads
|
||||
// will have the kRect quad type.
|
||||
static GrQuad make_2d_quad() {
|
||||
return GrQuad(SkRect::MakeLTRB(1.f, 2.f, 3.f, 4.f));
|
||||
}
|
||||
static bool is_2d_quad(const GrPerspQuad& quad) {
|
||||
return quad.x(0) == 1.f && quad.x(1) == 1.f && quad.x(2) == 3.f && quad.x(3) == 3.f &&
|
||||
quad.y(0) == 2.f && quad.y(1) == 4.f && quad.y(2) == 2.f && quad.y(3) == 4.f &&
|
||||
quad.w(0) == 1.f && quad.w(1) == 1.f && quad.w(2) == 1.f && quad.w(3) == 1.f;
|
||||
}
|
||||
|
||||
static GrPerspQuad make_2d_persp_quad() {
|
||||
return GrPerspQuad(SkRect::MakeLTRB(5.f, 6.f, 7.f, 8.f), SkMatrix::I());
|
||||
}
|
||||
static bool is_2d_persp_quad(const GrPerspQuad& quad) {
|
||||
return quad.x(0) == 5.f && quad.x(1) == 5.f && quad.x(2) == 7.f && quad.x(3) == 7.f &&
|
||||
quad.y(0) == 6.f && quad.y(1) == 8.f && quad.y(2) == 6.f && quad.y(3) == 8.f &&
|
||||
quad.w(0) == 1.f && quad.w(1) == 1.f && quad.w(2) == 1.f && quad.w(3) == 1.f;
|
||||
}
|
||||
|
||||
static GrPerspQuad make_3d_persp_quad() {
|
||||
// This perspective matrix leaves x and y unmodified, and sets w to the persp2 value
|
||||
SkMatrix p = SkMatrix::I();
|
||||
p[SkMatrix::kMPersp2] = 13.f;
|
||||
SkASSERT(p.hasPerspective()); // Sanity check
|
||||
return GrPerspQuad(SkRect::MakeLTRB(9.f, 10.f, 11.f, 12.f), p);
|
||||
}
|
||||
static bool is_3d_persp_quad(const GrPerspQuad& quad) {
|
||||
return quad.x(0) == 9.f && quad.x(1) == 9.f && quad.x(2) == 11.f && quad.x(3) == 11.f &&
|
||||
quad.y(0) == 10.f && quad.y(1) == 12.f && quad.y(2) == 10.f && quad.y(3) == 12.f &&
|
||||
quad.w(0) == 13.f && quad.w(1) == 13.f && quad.w(2) == 13.f && quad.w(3) == 13.f;
|
||||
}
|
||||
|
||||
TEST(Add2D) {
|
||||
GrQuadList list2D;
|
||||
// Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
|
||||
// the coordinates make sense (including that the type was lifted to perspective).
|
||||
list2D.push_back(make_2d_quad(), GrQuadType::kRect);
|
||||
list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
|
||||
|
||||
// Check 2D state of the list
|
||||
ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(list2D.quadType() == GrQuadType::kRect, "Unexpected quad type: %d",
|
||||
(uint32_t) list2D.quadType());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1");
|
||||
|
||||
// Force the 2D quads to be updated to store ws by adding a perspective quad
|
||||
list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
|
||||
"Expected 2D list to be upgraded to perspective");
|
||||
|
||||
// Re-check full state of list after type upgrade
|
||||
ASSERTF(list2D.count() == 3, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
|
||||
ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1 after upgrade");
|
||||
ASSERTF(is_3d_persp_quad(list2D[2]), "Incorrect quad at i=2");
|
||||
}
|
||||
|
||||
TEST(Add3D) {
|
||||
// Now make a list that starts with a 3D persp quad, then has conventional quads added to it
|
||||
// and make sure its state is correct
|
||||
GrQuadList list3D;
|
||||
list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
|
||||
list3D.push_back(make_2d_quad(), GrQuadType::kRect);
|
||||
|
||||
ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
|
||||
ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_persp_quad(list3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(is_2d_quad(list3D[2]), "Incorrect quad at i=2");
|
||||
}
|
||||
|
||||
TEST(AddWithMetadata2D) {
|
||||
// As above, but also make sure that the metadata is saved and read properly
|
||||
GrTQuadList<TestData> list2D;
|
||||
// Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
|
||||
// the coordinates make sense (including that the type was lifted to perspective).
|
||||
list2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
|
||||
list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
|
||||
|
||||
// Check 2D state of the list
|
||||
ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(list2D.quadType() == GrQuadType::kRect, "Unexpected quad type: %d",
|
||||
(uint32_t) list2D.quadType());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
|
||||
// Force the 2D quads to be updated to store ws by adding a perspective quad
|
||||
list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
|
||||
ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
|
||||
"Expected 2D list to be upgraded to perspective");
|
||||
|
||||
// Re-check full state of list after type upgrade
|
||||
ASSERTF(list2D.count() == 3, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
|
||||
ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1 after upgrade");
|
||||
ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
ASSERTF(is_3d_persp_quad(list2D[2]), "Incorrect quad at i=2");
|
||||
ASSERTF(list2D.metadata(2).fItem1 == 3 && list2D.metadata(2).fItem2 == 3.f,
|
||||
"Incorrect metadata at i=2");
|
||||
}
|
||||
|
||||
TEST(AddWithMetadata3D) {
|
||||
// Now make a list that starts with a 3D persp quad, then has conventional quads added to it
|
||||
// and make sure its state is correct
|
||||
GrTQuadList<TestData> list3D;
|
||||
list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
|
||||
list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
|
||||
list3D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
|
||||
|
||||
ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
|
||||
ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(list3D.metadata(0).fItem1 == 3 && list3D.metadata(0).fItem2 == 3.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_persp_quad(list3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(list3D.metadata(1).fItem1 == 2 && list3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
ASSERTF(is_2d_quad(list3D[2]), "Incorrect quad at i=2");
|
||||
ASSERTF(list3D.metadata(2).fItem1 == 1 && list3D.metadata(2).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=2");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith2D) {
|
||||
GrQuadList a2D;
|
||||
a2D.push_back(make_2d_quad(), GrQuadType::kRect);
|
||||
GrQuadList b2D;
|
||||
b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
|
||||
|
||||
a2D.concat(b2D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_persp_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith3D) {
|
||||
GrQuadList a2D;
|
||||
a2D.push_back(make_2d_quad(), GrQuadType::kRect);
|
||||
GrQuadList b3D;
|
||||
b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
|
||||
a2D.concat(b3D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_3d_persp_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith2D) {
|
||||
GrQuadList a3D;
|
||||
a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
GrQuadList b2D;
|
||||
b2D.push_back(make_2d_quad(), GrQuadType::kRect);
|
||||
|
||||
a3D.concat(b2D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith3D) {
|
||||
GrQuadList a3D;
|
||||
a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
GrQuadList b3D;
|
||||
b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
|
||||
|
||||
a3D.concat(b3D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_3d_persp_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith2DMetadata) {
|
||||
GrTQuadList<TestData> a2D;
|
||||
a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
|
||||
GrTQuadList<TestData> b2D;
|
||||
b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
|
||||
|
||||
a2D.concat(b2D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_persp_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith3DMetadata) {
|
||||
GrTQuadList<TestData> a2D;
|
||||
a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
|
||||
GrTQuadList<TestData> b3D;
|
||||
b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
|
||||
|
||||
a2D.concat(b3D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_3d_persp_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith2DMetadata) {
|
||||
GrTQuadList<TestData> a3D;
|
||||
a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
|
||||
GrTQuadList<TestData> b2D;
|
||||
b2D.push_back(make_2d_quad(), GrQuadType::kRect, {2, 2.f});
|
||||
|
||||
a3D.concat(b2D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith3DMetadata) {
|
||||
GrTQuadList<TestData> a3D;
|
||||
a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
|
||||
GrTQuadList<TestData> b3D;
|
||||
b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
|
||||
|
||||
a3D.concat(b3D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_3d_persp_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(WriteMetadata) {
|
||||
GrTQuadList<TestData> list;
|
||||
list.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
|
||||
ASSERTF(list.metadata(0).fItem1 == 1 && list.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0"); // Sanity check
|
||||
|
||||
// Rewrite metadata within the list and read back
|
||||
list.metadata(0).fItem1 = 2;
|
||||
list.metadata(0).fItem2 = 2.f;
|
||||
ASSERTF(list.metadata(0).fItem1 == 2 && list.metadata(0).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=0 after edit");
|
||||
}
|
Loading…
Reference in New Issue
Block a user