Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit cbe4e28

Browse files
johnstiles-googleSkia Commit-Bot
authored and
Skia Commit-Bot
committed
Fix strict-constraint bleed in strict_constraint_[batch_]no_red_allowed.
GrTextureOp was attempting to detect subset-rectangles that wouldn't affect the rendering output and could be ignored. Unfortunately, this optimization attempt had various flaws--small, one-pixel cracks on the edge of the border when AA was off, and highly-visible red fuzz on the edges of textures when MSAA was enabled. This CL limits the optimization to cases where the source and destination quads are axis-aligned rectangles, or cases where the inset is more than a half- pixel deep. This fix was made for both the single-image and batch drawing path, and generalized as much as possible to allow the code to be shared. This CL also cleans up the test code slightly. Bug: skia:10263, skia:10277 Change-Id: I200aaab47737b5ba0f559182ef4d0dfe0b719d50 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/291197 Commit-Queue: John Stiles <[email protected]> Reviewed-by: Michael Ludwig <[email protected]> Auto-Submit: John Stiles <[email protected]>
1 parent 6aa3869 commit cbe4e28

File tree

2 files changed

+76
-54
lines changed

2 files changed

+76
-54
lines changed

gm/bleed.cpp

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -219,68 +219,66 @@ class SrcRectConstraintGM : public skiagm::GM {
219219

220220
void onDraw(SkCanvas* canvas) override {
221221
canvas->clear(SK_ColorGRAY);
222-
SkTDArray<SkMatrix> matrices;
222+
std::vector<SkMatrix> matrices;
223223
// Draw with identity
224-
*matrices.append() = SkMatrix::I();
224+
matrices.push_back(SkMatrix::I());
225225

226226
// Draw with rotation and scale down in x, up in y.
227227
SkMatrix m;
228228
constexpr SkScalar kBottom = SkIntToScalar(kRow4Y + kBlockSize + kBlockSpacing);
229229
m.setTranslate(0, kBottom);
230230
m.preRotate(15.f, 0, kBottom + kBlockSpacing);
231231
m.preScale(0.71f, 1.22f);
232-
*matrices.append() = m;
232+
matrices.push_back(m);
233233

234234
// Align the next set with the middle of the previous in y, translated to the right in x.
235-
SkPoint corners[] = {{0, 0}, { 0, kBottom }, { kWidth, kBottom }, {kWidth, 0} };
236-
matrices[matrices.count()-1].mapPoints(corners, 4);
235+
SkPoint corners[] = {{0, 0}, {0, kBottom}, {kWidth, kBottom}, {kWidth, 0}};
236+
matrices.back().mapPoints(corners, 4);
237237
SkScalar y = (corners[0].fY + corners[1].fY + corners[2].fY + corners[3].fY) / 4;
238-
SkScalar x = std::max(std::max(corners[0].fX, corners[1].fX),
239-
std::max(corners[2].fX, corners[3].fX));
238+
SkScalar x = std::max({corners[0].fX, corners[1].fX, corners[2].fX, corners[3].fX});
240239
m.setTranslate(x, y);
241240
m.preScale(0.2f, 0.2f);
242-
*matrices.append() = m;
241+
matrices.push_back(m);
243242

244243
SkScalar maxX = 0;
245-
for (int antiAlias = 0; antiAlias < 2; ++antiAlias) {
244+
for (bool antiAlias : {false, true}) {
246245
canvas->save();
247246
canvas->translate(maxX, 0);
248-
for (int m = 0; m < matrices.count(); ++m) {
247+
for (const SkMatrix& matrix : matrices) {
249248
canvas->save();
250-
canvas->concat(matrices[m]);
251-
bool aa = SkToBool(antiAlias);
249+
canvas->concat(matrix);
252250

253251
// First draw a column with no filtering
254-
this->drawCase1(canvas, kCol0X, kRow0Y, aa, kNone_SkFilterQuality);
255-
this->drawCase2(canvas, kCol0X, kRow1Y, aa, kNone_SkFilterQuality);
256-
this->drawCase3(canvas, kCol0X, kRow2Y, aa, kNone_SkFilterQuality);
257-
this->drawCase4(canvas, kCol0X, kRow3Y, aa, kNone_SkFilterQuality);
258-
this->drawCase5(canvas, kCol0X, kRow4Y, aa, kNone_SkFilterQuality);
252+
this->drawCase1(canvas, kCol0X, kRow0Y, antiAlias, kNone_SkFilterQuality);
253+
this->drawCase2(canvas, kCol0X, kRow1Y, antiAlias, kNone_SkFilterQuality);
254+
this->drawCase3(canvas, kCol0X, kRow2Y, antiAlias, kNone_SkFilterQuality);
255+
this->drawCase4(canvas, kCol0X, kRow3Y, antiAlias, kNone_SkFilterQuality);
256+
this->drawCase5(canvas, kCol0X, kRow4Y, antiAlias, kNone_SkFilterQuality);
259257

260258
// Then draw a column with low filtering
261-
this->drawCase1(canvas, kCol1X, kRow0Y, aa, kLow_SkFilterQuality);
262-
this->drawCase2(canvas, kCol1X, kRow1Y, aa, kLow_SkFilterQuality);
263-
this->drawCase3(canvas, kCol1X, kRow2Y, aa, kLow_SkFilterQuality);
264-
this->drawCase4(canvas, kCol1X, kRow3Y, aa, kLow_SkFilterQuality);
265-
this->drawCase5(canvas, kCol1X, kRow4Y, aa, kLow_SkFilterQuality);
259+
this->drawCase1(canvas, kCol1X, kRow0Y, antiAlias, kLow_SkFilterQuality);
260+
this->drawCase2(canvas, kCol1X, kRow1Y, antiAlias, kLow_SkFilterQuality);
261+
this->drawCase3(canvas, kCol1X, kRow2Y, antiAlias, kLow_SkFilterQuality);
262+
this->drawCase4(canvas, kCol1X, kRow3Y, antiAlias, kLow_SkFilterQuality);
263+
this->drawCase5(canvas, kCol1X, kRow4Y, antiAlias, kLow_SkFilterQuality);
266264

267265
// Then draw a column with high filtering. Skip it if in kStrict mode and MIP
268266
// mapping will be used. On GPU we allow bleeding at non-base levels because
269267
// building a new MIP chain for the subset is expensive.
270268
SkScalar scales[2];
271-
SkAssertResult(matrices[m].getMinMaxScales(scales));
269+
SkAssertResult(matrix.getMinMaxScales(scales));
272270
if (fConstraint != SkCanvas::kStrict_SrcRectConstraint || scales[0] >= 1.f) {
273-
this->drawCase1(canvas, kCol2X, kRow0Y, aa, kHigh_SkFilterQuality);
274-
this->drawCase2(canvas, kCol2X, kRow1Y, aa, kHigh_SkFilterQuality);
275-
this->drawCase3(canvas, kCol2X, kRow2Y, aa, kHigh_SkFilterQuality);
276-
this->drawCase4(canvas, kCol2X, kRow3Y, aa, kHigh_SkFilterQuality);
277-
this->drawCase5(canvas, kCol2X, kRow4Y, aa, kHigh_SkFilterQuality);
271+
this->drawCase1(canvas, kCol2X, kRow0Y, antiAlias, kHigh_SkFilterQuality);
272+
this->drawCase2(canvas, kCol2X, kRow1Y, antiAlias, kHigh_SkFilterQuality);
273+
this->drawCase3(canvas, kCol2X, kRow2Y, antiAlias, kHigh_SkFilterQuality);
274+
this->drawCase4(canvas, kCol2X, kRow3Y, antiAlias, kHigh_SkFilterQuality);
275+
this->drawCase5(canvas, kCol2X, kRow4Y, antiAlias, kHigh_SkFilterQuality);
278276
}
279277

280-
SkPoint corners[] = { { 0, 0 },{ 0, kBottom },{ kWidth, kBottom },{ kWidth, 0 } };
281-
matrices[m].mapPoints(corners, 4);
282-
SkScalar x = kBlockSize + std::max(std::max(corners[0].fX, corners[1].fX),
283-
std::max(corners[2].fX, corners[3].fX));
278+
SkPoint innerCorners[] = {{0, 0}, {0, kBottom}, {kWidth, kBottom}, {kWidth, 0}};
279+
matrix.mapPoints(innerCorners, 4);
280+
SkScalar x = kBlockSize + std::max({innerCorners[0].fX, innerCorners[1].fX,
281+
innerCorners[2].fX, innerCorners[3].fX});
284282
maxX = std::max(maxX, x);
285283
canvas->restore();
286284
}

src/gpu/ops/GrTextureOp.cpp

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ static int proxy_run_count(const GrRenderTargetContext::TextureSetEntry set[], i
185185
return actualProxyRunCount;
186186
}
187187

188+
static bool safe_to_ignore_subset_rect(GrAAType aaType, GrSamplerState::Filter filter,
189+
const DrawQuad& quad, const SkRect& subsetRect) {
190+
// If both the device and local quad are both axis-aligned, and filtering is off, the local quad
191+
// can push all the way up to the edges of the the subset rect and the sampler shouldn't
192+
// overshoot. Unfortunately, antialiasing adds enough jitter that we can only rely on this in
193+
// the non-antialiased case.
194+
SkRect localBounds = quad.fLocal.bounds();
195+
if (aaType == GrAAType::kNone &&
196+
filter == GrSamplerState::Filter::kNearest &&
197+
quad.fDevice.quadType() == GrQuad::Type::kAxisAligned &&
198+
quad.fLocal.quadType() == GrQuad::Type::kAxisAligned &&
199+
subsetRect.contains(localBounds)) {
200+
201+
return true;
202+
}
203+
204+
// If the subset rect is inset by at least 0.5 pixels into the local quad's bounds, the
205+
// sampler shouldn't overshoot, even when antialiasing and filtering is taken into account.
206+
if (subsetRect.makeInset(0.5f, 0.5f).contains(localBounds)) {
207+
return true;
208+
}
209+
210+
// The subset rect cannot be ignored safely.
211+
return false;
212+
}
213+
188214
/**
189215
* Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a
190216
* the texture by color. The blend with the destination is always src-over. The edges are non-AA.
@@ -417,9 +443,7 @@ class TextureOp final : public GrMeshDrawOp {
417443
void allocatePrePreparedVertices(SkArenaAlloc* arena) {
418444
fPrePreparedVertices = arena->makeArrayDefault<char>(this->totalSizeInBytes());
419445
}
420-
421446
};
422-
423447
// If subsetRect is not null it will be used to apply a strict src rect-style constraint.
424448
TextureOp(GrSurfaceProxyView proxyView,
425449
sk_sp<GrColorSpaceXform> textureColorSpaceXform,
@@ -446,12 +470,12 @@ class TextureOp final : public GrMeshDrawOp {
446470
!subsetRect->contains(proxyView.proxy()->backingStoreBoundsRect()));
447471

448472
// We may have had a strict constraint with nearest filter solely due to possible AA bloat.
449-
// If we don't have (or determined we don't need) coverage AA then we can skip using a
450-
// subset.
451-
if (subsetRect && filter == GrSamplerState::Filter::kNearest &&
452-
aaType != GrAAType::kCoverage) {
453-
subsetRect = nullptr;
454-
fMetadata.fSubset = static_cast<uint16_t>(Subset::kNo);
473+
// Try to identify cases where the subsetting isn't actually necessary, and skip it.
474+
if (subsetRect) {
475+
if (safe_to_ignore_subset_rect(aaType, filter, *quad, *subsetRect)) {
476+
subsetRect = nullptr;
477+
fMetadata.fSubset = static_cast<uint16_t>(Subset::kNo);
478+
}
455479
}
456480

457481
// Normalize src coordinates and the subset (if set)
@@ -544,18 +568,14 @@ class TextureOp final : public GrMeshDrawOp {
544568
netFilter = GrSamplerState::Filter::kBilerp;
545569
}
546570

547-
// Normalize the src quads and apply origin
548-
NormalizationParams proxyParams = proxy_normalization_params(
549-
curProxy, set[q].fProxyView.origin());
550-
normalize_src_quad(proxyParams, &quad.fLocal);
551-
552571
// Update overall bounds of the op as the union of all quads
553572
bounds.joinPossiblyEmptyRect(quad.fDevice.bounds());
554573

555574
// Determine the AA type for the quad, then merge with net AA type
556575
GrAAType aaForQuad;
557576
GrQuadUtils::ResolveAAType(aaType, set[q].fAAFlags, quad.fDevice,
558577
&aaForQuad, &quad.fEdgeFlags);
578+
559579
// Resolve sets aaForQuad to aaType or None, there is never a change between aa methods
560580
SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType);
561581
if (netAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) {
@@ -565,17 +585,21 @@ class TextureOp final : public GrMeshDrawOp {
565585
// Calculate metadata for the entry
566586
const SkRect* subsetForQuad = nullptr;
567587
if (constraint == SkCanvas::kStrict_SrcRectConstraint) {
568-
// Check (briefly) if the strict constraint is needed for this set entry
569-
if (!set[q].fSrcRect.contains(curProxy->backingStoreBoundsRect()) &&
570-
(filter == GrSamplerState::Filter::kBilerp ||
571-
aaForQuad == GrAAType::kCoverage)) {
572-
// Can't rely on hardware clamping and the draw will access outer texels
573-
// for AA and/or bilerp. Unlike filter quality, this op still has per-quad
574-
// control over AA so that can check aaForQuad, not netAAType.
575-
netSubset = Subset::kYes;
576-
subsetForQuad = &set[q].fSrcRect;
588+
// Check (briefly) if the subset rect is actually needed for this set entry.
589+
SkRect* subsetRect = &set[q].fSrcRect;
590+
if (!subsetRect->contains(curProxy->backingStoreBoundsRect())) {
591+
if (!safe_to_ignore_subset_rect(aaForQuad, filter, quad, *subsetRect)) {
592+
netSubset = Subset::kYes;
593+
subsetForQuad = subsetRect;
594+
}
577595
}
578596
}
597+
598+
// Normalize the src quads and apply origin
599+
NormalizationParams proxyParams = proxy_normalization_params(
600+
curProxy, set[q].fProxyView.origin());
601+
normalize_src_quad(proxyParams, &quad.fLocal);
602+
579603
// This subset may represent a no-op, otherwise it will have the origin and dimensions
580604
// of the texture applied to it. Insetting for bilinear filtering is deferred until
581605
// on[Pre]Prepare so that the overall filter can be lazily determined.

0 commit comments

Comments
 (0)