Make GrTextureOp disable filtering/aa in more cases.
Previously we didn't do this at all for multiple-texture ops.
Improve the test detecting when filtering can be disabled.
Make draw_image_set GM create tiles with pixel of overlap for correct
filtering.
Add draw_image_set_rect_to_rect to exercise filtering/aa disablement
in combination with tiling.
Makes SkGpuDevice filter out inverted src rects (as is done implicitly
in SkBaseDevice by relying on drawImageRect).
Puts GrTextureOp::fFilter in bitfield.
Change-Id: Iee96cb54d665877c7f4aee422a3a7af2b249b1d6
Reviewed-on: https://skia-review.googlesource.com/c/161641
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 5e8cfca..9df0734 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1473,6 +1473,14 @@
}
};
for (int i = 0; i < count; ++i) {
+ // The default SkBaseDevice implementation is based on drawImageRect which does not allow
+ // non-sorted src rects. TODO: Decide this is OK or make sure we handle it.
+ if (!set[i].fSrcRect.isSorted()) {
+ draw();
+ base = i + 1;
+ n = 0;
+ continue;
+ }
textures[i].fProxy =
as_IB(set[i].fImage.get())
->asTextureProxyRef(fContext.get(), GrSamplerState::ClampBilerp(), nullptr,
@@ -1486,12 +1494,13 @@
draw();
base = i + 1;
n = 0;
- } else if (n > 0 &&
- (textures[i].fProxy->textureType() != textures[base].fProxy->textureType() ||
- textures[i].fProxy->config() != textures[base].fProxy->config() ||
- set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
- !SkColorSpace::Equals(set[i].fImage->colorSpace(),
- set[base].fImage->colorSpace()))) {
+ continue;
+ }
+ if (n > 0 &&
+ (textures[i].fProxy->textureType() != textures[base].fProxy->textureType() ||
+ textures[i].fProxy->config() != textures[base].fProxy->config() ||
+ set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
+ !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
draw();
base = i;
n = 1;
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 857b809..94e5ad5 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -571,6 +571,29 @@
DomainAssigner<V>::Assign(vertices, domain, filter, srcRect, origin, iw, ih);
}
+static bool aa_has_effect_for_rect_stays_rect(const GrPerspQuad& quad) {
+ SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
+ float ql = quad.x(0);
+ float qt = quad.y(0);
+ float qr = quad.x(3);
+ float qb = quad.y(3);
+ return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb);
+}
+
+static bool filter_has_effect_for_rect_stays_rect(const GrPerspQuad& quad, const SkRect& srcRect) {
+ SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
+ float ql = quad.x(0);
+ float qt = quad.y(0);
+ float qr = quad.x(3);
+ float qb = quad.y(3);
+ // Disable filtering when there is no scaling of the src rect and the src rect and dst rect
+ // align fractionally. If we allow inverted src rects this logic needs to consider that.
+ SkASSERT(srcRect.isSorted());
+ return (qr - ql) != srcRect.width() || (qb - qt) != srcRect.height() ||
+ SkScalarFraction(ql) != SkScalarFraction(srcRect.fLeft) ||
+ SkScalarFraction(qt) != SkScalarFraction(srcRect.fTop);
+}
+
/**
* Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a
* the texture by color. The blend with the destination is always src-over. The edges are non-AA.
@@ -681,7 +704,7 @@
: INHERITED(ClassID())
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
, fPaintColorSpaceXform(std::move(paintColorSpaceXform))
- , fFilter(filter)
+ , fFilter(static_cast<unsigned>(filter))
, fAAType(static_cast<unsigned>(aaType))
, fFinalized(0) {
switch (aaType) {
@@ -702,41 +725,31 @@
}
fPerspective = static_cast<unsigned>(viewMatrix.hasPerspective());
auto quad = GrPerspQuad(dstRect, viewMatrix);
- auto bounds = quad.bounds();
// We expect our caller to have already caught this optimization.
SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
constraint == SkCanvas::kFast_SrcRectConstraint);
if (viewMatrix.rectStaysRect()) {
- // Disable filtering when there is no scaling or fractional translation.
- // Disable coverage AA when rect falls on integers in device space.
- if (SkScalarIsInt(bounds.fLeft) && SkScalarIsInt(bounds.fTop) &&
- SkScalarIsInt(bounds.fRight) && SkScalarIsInt(bounds.fBottom)) {
- if (viewMatrix.isScaleTranslate()) {
- if (bounds.width() == srcRect.width() && bounds.height() == srcRect.height()) {
- fFilter = GrSamplerState::Filter::kNearest;
- }
- } else {
- if (bounds.width() == srcRect.height() && bounds.height() == srcRect.width()) {
- fFilter = GrSamplerState::Filter::kNearest;
- }
- }
- if (GrAAType::kCoverage == this->aaType()) {
- fAAType = static_cast<unsigned>(GrAAType::kNone);
- aaFlags = GrQuadAAFlags::kNone;
- }
+ if (this->aaType() == GrAAType::kCoverage && !aa_has_effect_for_rect_stays_rect(quad)) {
+ fAAType = static_cast<unsigned>(GrAAType::kNone);
+ aaFlags = GrQuadAAFlags::kNone;
+ }
+ if (this->filter() != GrSamplerState::Filter::kNearest &&
+ !filter_has_effect_for_rect_stays_rect(quad, srcRect)) {
+ fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
}
}
// We may have had a strict constraint with nearest filter solely due to possible AA bloat.
// If we don't have (or determined we don't need) coverage AA then we can skip using a
// domain.
if (constraint == SkCanvas::kStrict_SrcRectConstraint &&
- fFilter == GrSamplerState::Filter::kNearest &&
+ this->filter() == GrSamplerState::Filter::kNearest &&
this->aaType() != GrAAType::kCoverage) {
constraint = SkCanvas::kFast_SrcRectConstraint;
}
const auto& draw = fQuads.emplace_back(srcRect, quad, aaFlags, constraint, color);
fProxyCnt = 1;
fProxies[0] = {proxy.release(), 1};
+ auto bounds = quad.bounds();
this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
fDomain = static_cast<unsigned>(draw.domain());
fCanSkipAllocatorGather =
@@ -749,14 +762,16 @@
: INHERITED(ClassID())
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
, fPaintColorSpaceXform(std::move(paintColorSpaceXform))
- , fFilter(filter)
+ , fFilter(static_cast<unsigned>(filter))
, fAAType(static_cast<unsigned>(aaType))
, fFinalized(0) {
fQuads.reserve(cnt);
fProxyCnt = SkToUInt(cnt);
SkRect bounds = SkRectPriv::MakeLargestInverted();
- bool aa = false;
+ bool needAA = false;
+ bool mustFilter = false;
fCanSkipAllocatorGather = static_cast<unsigned>(true);
+ bool rectStaysRect = viewMatrix.rectStaysRect();
for (unsigned p = 0; p < fProxyCnt; ++p) {
fProxies[p].fProxy = SkRef(set[p].fProxy.get());
fProxies[p].fQuadCnt = 1;
@@ -773,6 +788,12 @@
aaFlags = GrQuadAAFlags::kNone;
break;
case GrAAType::kCoverage:
+ if (rectStaysRect) {
+ if (aaFlags != GrQuadAAFlags::kNone &&
+ !aa_has_effect_for_rect_stays_rect(quad)) {
+ aaFlags = GrQuadAAFlags::kNone;
+ }
+ }
break;
case GrAAType::kMSAA:
aaFlags = GrQuadAAFlags::kAll;
@@ -781,13 +802,20 @@
SK_ABORT("Should not use mixed sample AA");
break;
}
- aa = aa || (set[p].fAAFlags != GrQuadAAFlags::kNone);
+ needAA = needAA || (set[p].fAAFlags != GrQuadAAFlags::kNone);
+ if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
+ mustFilter = !rectStaysRect ||
+ filter_has_effect_for_rect_stays_rect(quad, set[p].fSrcRect);
+ }
fQuads.emplace_back(set[p].fSrcRect, quad, aaFlags, SkCanvas::kFast_SrcRectConstraint,
color);
}
- if (!aa) {
+ if (!needAA) {
fAAType = static_cast<unsigned>(GrAAType::kNone);
}
+ if (!mustFilter) {
+ fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
+ }
this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
fPerspective = static_cast<unsigned>(viewMatrix.hasPerspective());
fDomain = static_cast<unsigned>(false);
@@ -807,8 +835,8 @@
for (int i = start; i < start + cnt; ++i) {
const auto q = fQuads[i];
- tessellate_quad<Vertex>(q.quad(), q.aaFlags(), q.srcRect(), q.color(), origin, fFilter,
- vertices, iw, ih, q.domain());
+ tessellate_quad<Vertex>(q.quad(), q.aaFlags(), q.srcRect(), q.color(), origin,
+ this->filter(), vertices, iw, ih, q.domain());
vertices += 4;
}
}
@@ -840,7 +868,7 @@
bool coverageAA = GrAAType::kCoverage == this->aaType();
sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
- textureType, config, fFilter, std::move(fTextureColorSpaceXform),
+ textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
std::move(fPaintColorSpaceXform), coverageAA, hasPerspective, domain,
*target->caps().shaderCaps());
GrPipeline::InitArgs args;
@@ -996,6 +1024,7 @@
}
GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
+ GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
class Quad {
public:
@@ -1028,14 +1057,14 @@
SkSTArray<1, Quad, true> fQuads;
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
sk_sp<GrColorSpaceXform> fPaintColorSpaceXform;
- GrSamplerState::Filter fFilter;
+ unsigned fFilter : 2;
unsigned fAAType : 2;
unsigned fPerspective : 1;
unsigned fDomain : 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 - 6;
+ unsigned fProxyCnt : 32 - 8;
Proxy fProxies[1];
typedef GrMeshDrawOp INHERITED;