1/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include <new>
9
10#include "include/core/SkPoint.h"
11#include "include/core/SkPoint3.h"
12#include "include/gpu/GrRecordingContext.h"
13#include "include/private/SkFloatingPoint.h"
14#include "include/private/SkTo.h"
15#include "src/core/SkMathPriv.h"
16#include "src/core/SkMatrixPriv.h"
17#include "src/core/SkRectPriv.h"
18#include "src/gpu/GrAppliedClip.h"
19#include "src/gpu/GrCaps.h"
20#include "src/gpu/GrDrawOpTest.h"
21#include "src/gpu/GrGeometryProcessor.h"
22#include "src/gpu/GrGpu.h"
23#include "src/gpu/GrMemoryPool.h"
24#include "src/gpu/GrOpFlushState.h"
25#include "src/gpu/GrRecordingContextPriv.h"
26#include "src/gpu/GrResourceProvider.h"
27#include "src/gpu/GrResourceProviderPriv.h"
28#include "src/gpu/GrShaderCaps.h"
29#include "src/gpu/GrTexture.h"
30#include "src/gpu/GrTextureProxy.h"
31#include "src/gpu/SkGr.h"
32#include "src/gpu/effects/GrBlendFragmentProcessor.h"
33#include "src/gpu/effects/generated/GrClampFragmentProcessor.h"
34#include "src/gpu/geometry/GrQuad.h"
35#include "src/gpu/geometry/GrQuadBuffer.h"
36#include "src/gpu/geometry/GrQuadUtils.h"
37#include "src/gpu/glsl/GrGLSLVarying.h"
38#include "src/gpu/ops/GrFillRectOp.h"
39#include "src/gpu/ops/GrMeshDrawOp.h"
40#include "src/gpu/ops/GrQuadPerEdgeAA.h"
41#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
42#include "src/gpu/ops/GrTextureOp.h"
43
44namespace {
45
46using Subset = GrQuadPerEdgeAA::Subset;
47using VertexSpec = GrQuadPerEdgeAA::VertexSpec;
48using ColorType = GrQuadPerEdgeAA::ColorType;
49
50// Extracts lengths of vertical and horizontal edges of axis-aligned quad. "width" is the edge
51// between v0 and v2 (or v1 and v3), "height" is the edge between v0 and v1 (or v2 and v3).
52static SkSize axis_aligned_quad_size(const GrQuad& quad) {
53 SkASSERT(quad.quadType() == GrQuad::Type::kAxisAligned);
54 // Simplification of regular edge length equation, since it's axis aligned and can avoid sqrt
55 float dw = sk_float_abs(quad.x(2) - quad.x(0)) + sk_float_abs(quad.y(2) - quad.y(0));
56 float dh = sk_float_abs(quad.x(1) - quad.x(0)) + sk_float_abs(quad.y(1) - quad.y(0));
57 return {dw, dh};
58}
59
60static std::tuple<bool /* filter */,
61 bool /* mipmap */>
62filter_and_mm_have_effect(const GrQuad& srcQuad, const GrQuad& dstQuad) {
63 // If not axis-aligned in src or dst, then always say it has an effect
64 if (srcQuad.quadType() != GrQuad::Type::kAxisAligned ||
65 dstQuad.quadType() != GrQuad::Type::kAxisAligned) {
66 return {true, true};
67 }
68
69 SkRect srcRect;
70 SkRect dstRect;
71 if (srcQuad.asRect(&srcRect) && dstQuad.asRect(&dstRect)) {
72 // Disable filtering when there is no scaling (width and height are the same), and the
73 // top-left corners have the same fraction (so src and dst snap to the pixel grid
74 // identically).
75 SkASSERT(srcRect.isSorted());
76 bool filter = srcRect.width() != dstRect.width() || srcRect.height() != dstRect.height() ||
77 SkScalarFraction(srcRect.fLeft) != SkScalarFraction(dstRect.fLeft) ||
78 SkScalarFraction(srcRect.fTop) != SkScalarFraction(dstRect.fTop);
79 bool mm = srcRect.width() > dstRect.width() || srcRect.height() > dstRect.height();
80 return {filter, mm};
81 }
82 // Extract edge lengths
83 SkSize srcSize = axis_aligned_quad_size(srcQuad);
84 SkSize dstSize = axis_aligned_quad_size(dstQuad);
85 // Although the quads are axis-aligned, the local coordinate system is transformed such
86 // that fractionally-aligned sample centers will not align with the device coordinate system
87 // So disable filtering when edges are the same length and both srcQuad and dstQuad
88 // 0th vertex is integer aligned.
89 bool filter = srcSize != dstSize ||
90 !SkScalarIsInt(srcQuad.x(0)) ||
91 !SkScalarIsInt(srcQuad.y(0)) ||
92 !SkScalarIsInt(dstQuad.x(0)) ||
93 !SkScalarIsInt(dstQuad.y(0));
94 bool mm = srcSize.fWidth > dstSize.fWidth || srcSize.fHeight > dstSize.fHeight;
95 return {filter, mm};
96}
97
98// Describes function for normalizing src coords: [x * iw, y * ih + yOffset] can represent
99// regular and rectangular textures, w/ or w/o origin correction.
100struct NormalizationParams {
101 float fIW; // 1 / width of texture, or 1.0 for texture rectangles
102 float fInvH; // 1 / height of texture, or 1.0 for tex rects, X -1 if bottom-left origin
103 float fYOffset; // 0 for top-left origin, height of [normalized] tex if bottom-left
104};
105static NormalizationParams proxy_normalization_params(const GrSurfaceProxy* proxy,
106 GrSurfaceOrigin origin) {
107 // Whether or not the proxy is instantiated, this is the size its texture will be, so we can
108 // normalize the src coordinates up front.
109 SkISize dimensions = proxy->backingStoreDimensions();
110 float iw, ih, h;
111 if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
112 iw = ih = 1.f;
113 h = dimensions.height();
114 } else {
115 iw = 1.f / dimensions.width();
116 ih = 1.f / dimensions.height();
117 h = 1.f;
118 }
119
120 if (origin == kBottomLeft_GrSurfaceOrigin) {
121 return {iw, -ih, h};
122 } else {
123 return {iw, ih, 0.0f};
124 }
125}
126
127// Normalize the subset. If 'subsetRect' is null, it is assumed no subset constraint is desired,
128// so a sufficiently large rect is returned even if the quad ends up batched with an op that uses
129// subsets overall. When there is a subset it will be inset based on the filter mode. Normalization
130// and y-flipping are applied as indicated by NormalizationParams.
131static SkRect normalize_and_inset_subset(GrSamplerState::Filter filter,
132 const NormalizationParams& params,
133 const SkRect* subsetRect) {
134 static constexpr SkRect kLargeRect = {-100000, -100000, 1000000, 1000000};
135 if (!subsetRect) {
136 // Either the quad has no subset constraint and is batched with a subset constrained op
137 // (in which case we want a subset that doesn't restrict normalized tex coords), or the
138 // entire op doesn't use the subset, in which case the returned value is ignored.
139 return kLargeRect;
140 }
141
142 auto ltrb = skvx::Vec<4, float>::Load(subsetRect);
143 auto flipHi = skvx::Vec<4, float>({1.f, 1.f, -1.f, -1.f});
144 if (filter == GrSamplerState::Filter::kNearest) {
145 // Make sure our insetting puts us at pixel centers.
146 ltrb = skvx::floor(ltrb*flipHi)*flipHi;
147 }
148 // Inset with pin to the rect center.
149 ltrb += skvx::Vec<4, float>({.5f, .5f, -.5f, -.5f});
150 auto mid = (skvx::shuffle<2, 3, 0, 1>(ltrb) + ltrb)*0.5f;
151 ltrb = skvx::min(ltrb*flipHi, mid*flipHi)*flipHi;
152
153 // Normalize and offset
154 ltrb = ltrb * skvx::Vec<4, float>{params.fIW, params.fInvH, params.fIW, params.fInvH} +
155 skvx::Vec<4, float>{0.f, params.fYOffset, 0.f, params.fYOffset};
156 if (params.fInvH < 0.f) {
157 // Flip top and bottom to keep the rect sorted when loaded back to SkRect.
158 ltrb = skvx::shuffle<0, 3, 2, 1>(ltrb);
159 }
160
161 SkRect out;
162 ltrb.store(&out);
163 return out;
164}
165
166// Normalizes logical src coords and corrects for origin
167static void normalize_src_quad(const NormalizationParams& params,
168 GrQuad* srcQuad) {
169 // The src quad should not have any perspective
170 SkASSERT(!srcQuad->hasPerspective());
171 skvx::Vec<4, float> xs = srcQuad->x4f() * params.fIW;
172 skvx::Vec<4, float> ys = srcQuad->y4f() * params.fInvH + params.fYOffset;
173 xs.store(srcQuad->xs());
174 ys.store(srcQuad->ys());
175}
176
177// Count the number of proxy runs in the entry set. This usually is already computed by
178// SkGpuDevice, but when the BatchLengthLimiter chops the set up it must determine a new proxy count
179// for each split.
180static int proxy_run_count(const GrRenderTargetContext::TextureSetEntry set[], int count) {
181 int actualProxyRunCount = 0;
182 const GrSurfaceProxy* lastProxy = nullptr;
183 for (int i = 0; i < count; ++i) {
184 if (set[i].fProxyView.proxy() != lastProxy) {
185 actualProxyRunCount++;
186 lastProxy = set[i].fProxyView.proxy();
187 }
188 }
189 return actualProxyRunCount;
190}
191
192static bool safe_to_ignore_subset_rect(GrAAType aaType, GrSamplerState::Filter filter,
193 const DrawQuad& quad, const SkRect& subsetRect) {
194 // If both the device and local quad are both axis-aligned, and filtering is off, the local quad
195 // can push all the way up to the edges of the the subset rect and the sampler shouldn't
196 // overshoot. Unfortunately, antialiasing adds enough jitter that we can only rely on this in
197 // the non-antialiased case.
198 SkRect localBounds = quad.fLocal.bounds();
199 if (aaType == GrAAType::kNone &&
200 filter == GrSamplerState::Filter::kNearest &&
201 quad.fDevice.quadType() == GrQuad::Type::kAxisAligned &&
202 quad.fLocal.quadType() == GrQuad::Type::kAxisAligned &&
203 subsetRect.contains(localBounds)) {
204
205 return true;
206 }
207
208 // If the subset rect is inset by at least 0.5 pixels into the local quad's bounds, the
209 // sampler shouldn't overshoot, even when antialiasing and filtering is taken into account.
210 if (subsetRect.makeInset(0.5f, 0.5f).contains(localBounds)) {
211 return true;
212 }
213
214 // The subset rect cannot be ignored safely.
215 return false;
216}
217
218/**
219 * Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a
220 * the texture by color. The blend with the destination is always src-over. The edges are non-AA.
221 */
222class TextureOp final : public GrMeshDrawOp {
223public:
224 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
225 GrSurfaceProxyView proxyView,
226 sk_sp<GrColorSpaceXform> textureXform,
227 GrSamplerState::Filter filter,
228 GrSamplerState::MipmapMode mm,
229 const SkPMColor4f& color,
230 GrTextureOp::Saturate saturate,
231 GrAAType aaType,
232 DrawQuad* quad,
233 const SkRect* subset) {
234 GrOpMemoryPool* pool = context->priv().opMemoryPool();
235 return pool->allocate<TextureOp>(std::move(proxyView), std::move(textureXform), filter, mm,
236 color, saturate, aaType, quad, subset);
237 }
238
239 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
240 GrRenderTargetContext::TextureSetEntry set[],
241 int cnt,
242 int proxyRunCnt,
243 GrSamplerState::Filter filter,
244 GrSamplerState::MipmapMode mm,
245 GrTextureOp::Saturate saturate,
246 GrAAType aaType,
247 SkCanvas::SrcRectConstraint constraint,
248 const SkMatrix& viewMatrix,
249 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
250 // Allocate size based on proxyRunCnt, since that determines number of ViewCountPairs.
251 SkASSERT(proxyRunCnt <= cnt);
252
253 size_t size = sizeof(TextureOp) + sizeof(ViewCountPair) * (proxyRunCnt - 1);
254 GrOpMemoryPool* pool = context->priv().opMemoryPool();
255 void* mem = pool->allocate(size);
256 return std::unique_ptr<GrDrawOp>(
257 new (mem) TextureOp(set, cnt, proxyRunCnt, filter, mm, saturate, aaType, constraint,
258 viewMatrix, std::move(textureColorSpaceXform)));
259 }
260
261 ~TextureOp() override {
262 for (unsigned p = 1; p < fMetadata.fProxyCount; ++p) {
263 fViewCountPairs[p].~ViewCountPair();
264 }
265 }
266
267 const char* name() const override { return "TextureOp"; }
268
269 void visitProxies(const VisitProxyFunc& func) const override {
270 bool mipped = (fMetadata.mipmapMode() != GrSamplerState::MipmapMode::kNone);
271 for (unsigned p = 0; p < fMetadata.fProxyCount; ++p) {
272 func(fViewCountPairs[p].fProxy.get(), GrMipmapped(mipped));
273 }
274 if (fDesc && fDesc->fProgramInfo) {
275 fDesc->fProgramInfo->visitFPProxies(func);
276 }
277 }
278
279#ifdef SK_DEBUG
280 static void ValidateResourceLimits() {
281 // The op implementation has an upper bound on the number of quads that it can represent.
282 // However, the resource manager imposes its own limit on the number of quads, which should
283 // always be lower than the numerical limit this op can hold.
284 using CountStorage = decltype(Metadata::fTotalQuadCount);
285 CountStorage maxQuadCount = std::numeric_limits<CountStorage>::max();
286 // GrResourceProvider::Max...() is typed as int, so don't compare across signed/unsigned.
287 int resourceLimit = SkTo<int>(maxQuadCount);
288 SkASSERT(GrResourceProvider::MaxNumAAQuads() <= resourceLimit &&
289 GrResourceProvider::MaxNumNonAAQuads() <= resourceLimit);
290 }
291#endif
292
293 GrProcessorSet::Analysis finalize(
294 const GrCaps& caps, const GrAppliedClip*, bool hasMixedSampledCoverage,
295 GrClampType clampType) override {
296 SkASSERT(fMetadata.colorType() == ColorType::kNone);
297 auto iter = fQuads.metadata();
298 while(iter.next()) {
299 auto colorType = GrQuadPerEdgeAA::MinColorType(iter->fColor);
300 fMetadata.fColorType = std::max(fMetadata.fColorType, static_cast<uint16_t>(colorType));
301 }
302 return GrProcessorSet::EmptySetAnalysis();
303 }
304
305 FixedFunctionFlags fixedFunctionFlags() const override {
306 return fMetadata.aaType() == GrAAType::kMSAA ? FixedFunctionFlags::kUsesHWAA
307 : FixedFunctionFlags::kNone;
308 }
309
310 DEFINE_OP_CLASS_ID
311
312private:
313 friend class ::GrOpMemoryPool;
314
315 struct ColorSubsetAndAA {
316 ColorSubsetAndAA(const SkPMColor4f& color, const SkRect& subsetRect, GrQuadAAFlags aaFlags)
317 : fColor(color)
318 , fSubsetRect(subsetRect)
319 , fAAFlags(static_cast<uint16_t>(aaFlags)) {
320 SkASSERT(fAAFlags == static_cast<uint16_t>(aaFlags));
321 }
322
323 SkPMColor4f fColor;
324 // If the op doesn't use subsets, this is ignored. If the op uses subsets and the specific
325 // entry does not, this rect will equal kLargeRect, so it automatically has no effect.
326 SkRect fSubsetRect;
327 unsigned fAAFlags : 4;
328
329 GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
330 };
331
332 struct ViewCountPair {
333 // Normally this would be a GrSurfaceProxyView, but GrTextureOp applies the GrOrigin right
334 // away so it doesn't need to be stored, and all ViewCountPairs in an op have the same
335 // swizzle so that is stored in the op metadata.
336 sk_sp<GrSurfaceProxy> fProxy;
337 int fQuadCnt;
338 };
339
340 // TextureOp and ViewCountPair are 8 byte aligned. This is packed into 8 bytes to minimally
341 // increase the size of the op; increasing the op size can have a surprising impact on
342 // performance (since texture ops are one of the most commonly used in an app).
343 struct Metadata {
344 // AAType must be filled after initialization; ColorType is determined in finalize()
345 Metadata(const GrSwizzle& swizzle,
346 GrSamplerState::Filter filter,
347 GrSamplerState::MipmapMode mm,
348 GrQuadPerEdgeAA::Subset subset,
349 GrTextureOp::Saturate saturate)
350 : fSwizzle(swizzle)
351 , fProxyCount(1)
352 , fTotalQuadCount(1)
353 , fFilter(static_cast<uint16_t>(filter))
354 , fMipmapMode(static_cast<uint16_t>(mm))
355 , fAAType(static_cast<uint16_t>(GrAAType::kNone))
356 , fColorType(static_cast<uint16_t>(ColorType::kNone))
357 , fSubset(static_cast<uint16_t>(subset))
358 , fSaturate(static_cast<uint16_t>(saturate)) {}
359
360 GrSwizzle fSwizzle; // sizeof(GrSwizzle) == uint16_t
361 uint16_t fProxyCount;
362 // This will be >= fProxyCount, since a proxy may be drawn multiple times
363 uint16_t fTotalQuadCount;
364
365 // These must be based on uint16_t to help MSVC's pack bitfields optimally
366 uint16_t fFilter : 2; // GrSamplerState::Filter
367 uint16_t fMipmapMode : 2; // GrSamplerState::MipmapMode
368 uint16_t fAAType : 2; // GrAAType
369 uint16_t fColorType : 2; // GrQuadPerEdgeAA::ColorType
370 uint16_t fSubset : 1; // bool
371 uint16_t fSaturate : 1; // bool
372 uint16_t fUnused : 6; // # of bits left before Metadata exceeds 8 bytes
373
374 GrSamplerState::Filter filter() const {
375 return static_cast<GrSamplerState::Filter>(fFilter);
376 }
377 GrSamplerState::MipmapMode mipmapMode() const {
378 return static_cast<GrSamplerState::MipmapMode>(fMipmapMode);
379 }
380 GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
381 ColorType colorType() const { return static_cast<ColorType>(fColorType); }
382 Subset subset() const { return static_cast<Subset>(fSubset); }
383 GrTextureOp::Saturate saturate() const {
384 return static_cast<GrTextureOp::Saturate>(fSaturate);
385 }
386
387 static_assert(GrSamplerState::kFilterCount <= 4);
388 static_assert(kGrAATypeCount <= 4);
389 static_assert(GrQuadPerEdgeAA::kColorTypeCount <= 4);
390 };
391 static_assert(sizeof(Metadata) == 8);
392
393 // This descriptor is used to store the draw info we decide on during on(Pre)PrepareDraws. We
394 // store the data in a separate struct in order to minimize the size of the TextureOp.
395 // Historically, increasing the TextureOp's size has caused surprising perf regressions, but we
396 // may want to re-evaluate whether this is still necessary.
397 //
398 // In the onPrePrepareDraws case it is allocated in the creation-time opData arena, and
399 // allocatePrePreparedVertices is also called.
400 //
401 // In the onPrepareDraws case this descriptor is allocated in the flush-time arena (i.e., as
402 // part of the flushState).
403 struct Desc {
404 VertexSpec fVertexSpec;
405 int fNumProxies = 0;
406 int fNumTotalQuads = 0;
407
408 // This member variable is only used by 'onPrePrepareDraws'.
409 char* fPrePreparedVertices = nullptr;
410
411 GrProgramInfo* fProgramInfo = nullptr;
412
413 sk_sp<const GrBuffer> fIndexBuffer;
414 sk_sp<const GrBuffer> fVertexBuffer;
415 int fBaseVertex;
416
417 // How big should 'fVertices' be to hold all the vertex data?
418 size_t totalSizeInBytes() const {
419 return this->totalNumVertices() * fVertexSpec.vertexSize();
420 }
421
422 int totalNumVertices() const {
423 return fNumTotalQuads * fVertexSpec.verticesPerQuad();
424 }
425
426 void allocatePrePreparedVertices(SkArenaAlloc* arena) {
427 fPrePreparedVertices = arena->makeArrayDefault<char>(this->totalSizeInBytes());
428 }
429 };
430 // If subsetRect is not null it will be used to apply a strict src rect-style constraint.
431 TextureOp(GrSurfaceProxyView proxyView,
432 sk_sp<GrColorSpaceXform> textureColorSpaceXform,
433 GrSamplerState::Filter filter,
434 GrSamplerState::MipmapMode mm,
435 const SkPMColor4f& color,
436 GrTextureOp::Saturate saturate,
437 GrAAType aaType,
438 DrawQuad* quad,
439 const SkRect* subsetRect)
440 : INHERITED(ClassID())
441 , fQuads(1, true /* includes locals */)
442 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
443 , fDesc(nullptr)
444 , fMetadata(proxyView.swizzle(), filter, mm, Subset(!!subsetRect), saturate) {
445 // Clean up disparities between the overall aa type and edge configuration and apply
446 // optimizations based on the rect and matrix when appropriate
447 GrQuadUtils::ResolveAAType(aaType, quad->fEdgeFlags, quad->fDevice,
448 &aaType, &quad->fEdgeFlags);
449 fMetadata.fAAType = static_cast<uint16_t>(aaType);
450
451 // We expect our caller to have already caught this optimization.
452 SkASSERT(!subsetRect ||
453 !subsetRect->contains(proxyView.proxy()->backingStoreBoundsRect()));
454
455 // We may have had a strict constraint with nearest filter solely due to possible AA bloat.
456 // Try to identify cases where the subsetting isn't actually necessary, and skip it.
457 if (subsetRect) {
458 if (safe_to_ignore_subset_rect(aaType, filter, *quad, *subsetRect)) {
459 subsetRect = nullptr;
460 fMetadata.fSubset = static_cast<uint16_t>(Subset::kNo);
461 }
462 }
463
464 // Normalize src coordinates and the subset (if set)
465 NormalizationParams params = proxy_normalization_params(proxyView.proxy(),
466 proxyView.origin());
467 normalize_src_quad(params, &quad->fLocal);
468 SkRect subset = normalize_and_inset_subset(filter, params, subsetRect);
469
470 // Set bounds before clipping so we don't have to worry about unioning the bounds of
471 // the two potential quads (GrQuad::bounds() is perspective-safe).
472 this->setBounds(quad->fDevice.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
473 IsHairline::kNo);
474
475 int quadCount = this->appendQuad(quad, color, subset);
476 fViewCountPairs[0] = {proxyView.detachProxy(), quadCount};
477 }
478
479 TextureOp(GrRenderTargetContext::TextureSetEntry set[],
480 int cnt,
481 int proxyRunCnt,
482 GrSamplerState::Filter filter,
483 GrSamplerState::MipmapMode mm,
484 GrTextureOp::Saturate saturate,
485 GrAAType aaType,
486 SkCanvas::SrcRectConstraint constraint,
487 const SkMatrix& viewMatrix,
488 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
489 : INHERITED(ClassID())
490 , fQuads(cnt, true /* includes locals */)
491 , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
492 , fDesc(nullptr)
493 , fMetadata(set[0].fProxyView.swizzle(),
494 GrSamplerState::Filter::kNearest,
495 GrSamplerState::MipmapMode::kNone,
496 Subset::kNo,
497 saturate) {
498 // Update counts to reflect the batch op
499 fMetadata.fProxyCount = SkToUInt(proxyRunCnt);
500 fMetadata.fTotalQuadCount = SkToUInt(cnt);
501
502 SkRect bounds = SkRectPriv::MakeLargestInverted();
503
504 GrAAType netAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
505 Subset netSubset = Subset::kNo;
506 GrSamplerState::Filter netFilter = GrSamplerState::Filter::kNearest;
507 GrSamplerState::MipmapMode netMM = GrSamplerState::MipmapMode::kNone;
508
509 const GrSurfaceProxy* curProxy = nullptr;
510
511 // 'q' is the index in 'set' and fQuadBuffer; 'p' is the index in fViewCountPairs and only
512 // increases when set[q]'s proxy changes.
513 int p = 0;
514 for (int q = 0; q < cnt; ++q) {
515 SkASSERT(mm == GrSamplerState::MipmapMode::kNone ||
516 (set[0].fProxyView.proxy()->asTextureProxy()->mipmapped() ==
517 GrMipmapped::kYes));
518 if (q == 0) {
519 // We do not placement new the first ViewCountPair since that one is allocated and
520 // initialized as part of the GrTextureOp creation.
521 fViewCountPairs[0].fProxy = set[0].fProxyView.detachProxy();
522 fViewCountPairs[0].fQuadCnt = 0;
523 curProxy = fViewCountPairs[0].fProxy.get();
524 } else if (set[q].fProxyView.proxy() != curProxy) {
525 // We must placement new the ViewCountPairs here so that the sk_sps in the
526 // GrSurfaceProxyView get initialized properly.
527 new(&fViewCountPairs[++p])ViewCountPair({set[q].fProxyView.detachProxy(), 0});
528
529 curProxy = fViewCountPairs[p].fProxy.get();
530 SkASSERT(GrTextureProxy::ProxiesAreCompatibleAsDynamicState(
531 curProxy, fViewCountPairs[0].fProxy.get()));
532 SkASSERT(fMetadata.fSwizzle == set[q].fProxyView.swizzle());
533 } // else another quad referencing the same proxy
534
535 SkMatrix ctm = viewMatrix;
536 if (set[q].fPreViewMatrix) {
537 ctm.preConcat(*set[q].fPreViewMatrix);
538 }
539
540 // Use dstRect/srcRect unless dstClip is provided, in which case derive new source
541 // coordinates by mapping dstClipQuad by the dstRect to srcRect transform.
542 DrawQuad quad;
543 if (set[q].fDstClipQuad) {
544 quad.fDevice = GrQuad::MakeFromSkQuad(set[q].fDstClipQuad, ctm);
545
546 SkPoint srcPts[4];
547 GrMapRectPoints(set[q].fDstRect, set[q].fSrcRect, set[q].fDstClipQuad, srcPts, 4);
548 quad.fLocal = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
549 } else {
550 quad.fDevice = GrQuad::MakeFromRect(set[q].fDstRect, ctm);
551 quad.fLocal = GrQuad(set[q].fSrcRect);
552 }
553
554 if (netFilter != filter || netMM != mm) {
555 // The only way netFilter != filter is if linear is requested and we haven't yet
556 // found a quad that requires linear (so net is still nearest). Similar for mip
557 // mapping.
558 SkASSERT(filter == netFilter ||
559 (netFilter == GrSamplerState::Filter::kNearest && filter > netFilter));
560 SkASSERT(mm == netMM ||
561 (netMM == GrSamplerState::MipmapMode::kNone && mm > netMM));
562 auto [mustFilter, mustMM] = filter_and_mm_have_effect(quad.fLocal, quad.fDevice);
563 if (mustFilter && filter != GrSamplerState::Filter::kNearest) {
564 netFilter = filter;
565 }
566 if (mustMM && mm != GrSamplerState::MipmapMode::kNone) {
567 netMM = mm;
568 }
569 }
570
571 // Update overall bounds of the op as the union of all quads
572 bounds.joinPossiblyEmptyRect(quad.fDevice.bounds());
573
574 // Determine the AA type for the quad, then merge with net AA type
575 GrAAType aaForQuad;
576 GrQuadUtils::ResolveAAType(aaType, set[q].fAAFlags, quad.fDevice,
577 &aaForQuad, &quad.fEdgeFlags);
578
579 // Resolve sets aaForQuad to aaType or None, there is never a change between aa methods
580 SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType);
581 if (netAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) {
582 netAAType = aaType;
583 }
584
585 // Calculate metadata for the entry
586 const SkRect* subsetForQuad = nullptr;
587 if (constraint == SkCanvas::kStrict_SrcRectConstraint) {
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 }
595 }
596 }
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
603 // This subset may represent a no-op, otherwise it will have the origin and dimensions
604 // of the texture applied to it. Insetting for bilinear filtering is deferred until
605 // on[Pre]Prepare so that the overall filter can be lazily determined.
606 SkRect subset = normalize_and_inset_subset(filter, proxyParams, subsetForQuad);
607
608 // Always append a quad (or 2 if perspective clipped), it just may refer back to a prior
609 // ViewCountPair (this frequently happens when Chrome draws 9-patches).
610 fViewCountPairs[p].fQuadCnt += this->appendQuad(&quad, set[q].fColor, subset);
611 }
612 // The # of proxy switches should match what was provided (+1 because we incremented p
613 // when a new proxy was encountered).
614 SkASSERT((p + 1) == fMetadata.fProxyCount);
615 SkASSERT(fQuads.count() == fMetadata.fTotalQuadCount);
616
617 fMetadata.fAAType = static_cast<uint16_t>(netAAType);
618 fMetadata.fFilter = static_cast<uint16_t>(netFilter);
619 fMetadata.fSubset = static_cast<uint16_t>(netSubset);
620
621 this->setBounds(bounds, HasAABloat(netAAType == GrAAType::kCoverage), IsHairline::kNo);
622 }
623
624 int appendQuad(DrawQuad* quad, const SkPMColor4f& color, const SkRect& subset) {
625 DrawQuad extra;
626 // Only clip when there's anti-aliasing. When non-aa, the GPU clips just fine and there's
627 // no inset/outset math that requires w > 0.
628 int quadCount = quad->fEdgeFlags != GrQuadAAFlags::kNone ?
629 GrQuadUtils::ClipToW0(quad, &extra) : 1;
630 if (quadCount == 0) {
631 // We can't discard the op at this point, but disable AA flags so it won't go through
632 // inset/outset processing
633 quad->fEdgeFlags = GrQuadAAFlags::kNone;
634 quadCount = 1;
635 }
636 fQuads.append(quad->fDevice, {color, subset, quad->fEdgeFlags}, &quad->fLocal);
637 if (quadCount > 1) {
638 fQuads.append(extra.fDevice, {color, subset, extra.fEdgeFlags}, &extra.fLocal);
639 fMetadata.fTotalQuadCount++;
640 }
641 return quadCount;
642 }
643
644 GrProgramInfo* programInfo() override {
645 // Although this Op implements its own onPrePrepareDraws it calls GrMeshDrawOps' version so
646 // this entry point will be called.
647 return (fDesc) ? fDesc->fProgramInfo : nullptr;
648 }
649
650 void onCreateProgramInfo(const GrCaps* caps,
651 SkArenaAlloc* arena,
652 const GrSurfaceProxyView* writeView,
653 GrAppliedClip&& appliedClip,
654 const GrXferProcessor::DstProxyView& dstProxyView) override {
655 SkASSERT(fDesc);
656
657 GrGeometryProcessor* gp;
658
659 {
660 const GrBackendFormat& backendFormat =
661 fViewCountPairs[0].fProxy->backendFormat();
662
663 GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
664 fMetadata.filter());
665
666 gp = GrQuadPerEdgeAA::MakeTexturedProcessor(
667 arena, fDesc->fVertexSpec, *caps->shaderCaps(), backendFormat, samplerState,
668 fMetadata.fSwizzle, std::move(fTextureColorSpaceXform), fMetadata.saturate());
669
670 SkASSERT(fDesc->fVertexSpec.vertexSize() == gp->vertexStride());
671 }
672
673 auto pipelineFlags = (GrAAType::kMSAA == fMetadata.aaType()) ?
674 GrPipeline::InputFlags::kHWAntialias : GrPipeline::InputFlags::kNone;
675
676 fDesc->fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(
677 caps, arena, writeView, std::move(appliedClip), dstProxyView, gp,
678 GrProcessorSet::MakeEmptySet(), fDesc->fVertexSpec.primitiveType(),
679 pipelineFlags);
680 }
681
682 void onPrePrepareDraws(GrRecordingContext* context,
683 const GrSurfaceProxyView* writeView,
684 GrAppliedClip* clip,
685 const GrXferProcessor::DstProxyView& dstProxyView) override {
686 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
687
688 SkDEBUGCODE(this->validate();)
689 SkASSERT(!fDesc);
690
691 SkArenaAlloc* arena = context->priv().recordTimeAllocator();
692
693 fDesc = arena->make<Desc>();
694 this->characterize(fDesc);
695 fDesc->allocatePrePreparedVertices(arena);
696 FillInVertices(*context->priv().caps(), this, fDesc, fDesc->fPrePreparedVertices);
697
698 // This will call onCreateProgramInfo and register the created program with the DDL.
699 this->INHERITED::onPrePrepareDraws(context, writeView, clip, dstProxyView);
700 }
701
702 static void FillInVertices(const GrCaps& caps, TextureOp* texOp, Desc* desc, char* vertexData) {
703 SkASSERT(vertexData);
704
705 int totQuadsSeen = 0;
706 SkDEBUGCODE(int totVerticesSeen = 0;)
707 SkDEBUGCODE(const size_t vertexSize = desc->fVertexSpec.vertexSize());
708
709 GrQuadPerEdgeAA::Tessellator tessellator(desc->fVertexSpec, vertexData);
710 for (const auto& op : ChainRange<TextureOp>(texOp)) {
711 auto iter = op.fQuads.iterator();
712 for (unsigned p = 0; p < op.fMetadata.fProxyCount; ++p) {
713 const int quadCnt = op.fViewCountPairs[p].fQuadCnt;
714 SkDEBUGCODE(int meshVertexCnt = quadCnt * desc->fVertexSpec.verticesPerQuad());
715
716 for (int i = 0; i < quadCnt && iter.next(); ++i) {
717 SkASSERT(iter.isLocalValid());
718 const ColorSubsetAndAA& info = iter.metadata();
719
720 tessellator.append(iter.deviceQuad(), iter.localQuad(), info.fColor,
721 info.fSubsetRect, info.aaFlags());
722 }
723
724 SkASSERT((totVerticesSeen + meshVertexCnt) * vertexSize
725 == (size_t)(tessellator.vertices() - vertexData));
726
727 totQuadsSeen += quadCnt;
728 SkDEBUGCODE(totVerticesSeen += meshVertexCnt);
729 SkASSERT(totQuadsSeen * desc->fVertexSpec.verticesPerQuad() == totVerticesSeen);
730 }
731
732 // If quad counts per proxy were calculated correctly, the entire iterator
733 // should have been consumed.
734 SkASSERT(!iter.next());
735 }
736
737 SkASSERT(desc->totalSizeInBytes() == (size_t)(tessellator.vertices() - vertexData));
738 SkASSERT(totQuadsSeen == desc->fNumTotalQuads);
739 SkASSERT(totVerticesSeen == desc->totalNumVertices());
740 }
741
742#ifdef SK_DEBUG
743 static int validate_op(GrTextureType textureType,
744 GrAAType aaType,
745 GrSwizzle swizzle,
746 const TextureOp* op) {
747 SkASSERT(op->fMetadata.fSwizzle == swizzle);
748
749 int quadCount = 0;
750 for (unsigned p = 0; p < op->fMetadata.fProxyCount; ++p) {
751 auto* proxy = op->fViewCountPairs[p].fProxy->asTextureProxy();
752 quadCount += op->fViewCountPairs[p].fQuadCnt;
753 SkASSERT(proxy);
754 SkASSERT(proxy->textureType() == textureType);
755 }
756
757 SkASSERT(aaType == op->fMetadata.aaType());
758 return quadCount;
759 }
760
761 void validate() const override {
762 // NOTE: Since this is debug-only code, we use the virtual asTextureProxy()
763 auto textureType = fViewCountPairs[0].fProxy->asTextureProxy()->textureType();
764 GrAAType aaType = fMetadata.aaType();
765 GrSwizzle swizzle = fMetadata.fSwizzle;
766
767 int quadCount = validate_op(textureType, aaType, swizzle, this);
768
769 for (const GrOp* tmp = this->prevInChain(); tmp; tmp = tmp->prevInChain()) {
770 quadCount += validate_op(textureType, aaType, swizzle,
771 static_cast<const TextureOp*>(tmp));
772 }
773
774 for (const GrOp* tmp = this->nextInChain(); tmp; tmp = tmp->nextInChain()) {
775 quadCount += validate_op(textureType, aaType, swizzle,
776 static_cast<const TextureOp*>(tmp));
777 }
778
779 SkASSERT(quadCount == this->numChainedQuads());
780 }
781
782#endif
783
784#if GR_TEST_UTILS
785 int numQuads() const final { return this->totNumQuads(); }
786#endif
787
788 void characterize(Desc* desc) const {
789 SkDEBUGCODE(this->validate();)
790
791 GrQuad::Type quadType = GrQuad::Type::kAxisAligned;
792 ColorType colorType = ColorType::kNone;
793 GrQuad::Type srcQuadType = GrQuad::Type::kAxisAligned;
794 Subset subset = Subset::kNo;
795 GrAAType overallAAType = fMetadata.aaType();
796
797 desc->fNumProxies = 0;
798 desc->fNumTotalQuads = 0;
799 int maxQuadsPerMesh = 0;
800
801 for (const auto& op : ChainRange<TextureOp>(this)) {
802 if (op.fQuads.deviceQuadType() > quadType) {
803 quadType = op.fQuads.deviceQuadType();
804 }
805 if (op.fQuads.localQuadType() > srcQuadType) {
806 srcQuadType = op.fQuads.localQuadType();
807 }
808 if (op.fMetadata.subset() == Subset::kYes) {
809 subset = Subset::kYes;
810 }
811 colorType = std::max(colorType, op.fMetadata.colorType());
812 desc->fNumProxies += op.fMetadata.fProxyCount;
813
814 for (unsigned p = 0; p < op.fMetadata.fProxyCount; ++p) {
815 maxQuadsPerMesh = std::max(op.fViewCountPairs[p].fQuadCnt, maxQuadsPerMesh);
816 }
817 desc->fNumTotalQuads += op.totNumQuads();
818
819 if (op.fMetadata.aaType() == GrAAType::kCoverage) {
820 overallAAType = GrAAType::kCoverage;
821 }
822 }
823
824 SkASSERT(desc->fNumTotalQuads == this->numChainedQuads());
825
826 SkASSERT(!CombinedQuadCountWillOverflow(overallAAType, false, desc->fNumTotalQuads));
827
828 auto indexBufferOption = GrQuadPerEdgeAA::CalcIndexBufferOption(overallAAType,
829 maxQuadsPerMesh);
830
831 desc->fVertexSpec = VertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true,
832 subset, overallAAType, /* alpha as coverage */ true,
833 indexBufferOption);
834
835 SkASSERT(desc->fNumTotalQuads <= GrQuadPerEdgeAA::QuadLimit(indexBufferOption));
836 }
837
838 int totNumQuads() const {
839#ifdef SK_DEBUG
840 int tmp = 0;
841 for (unsigned p = 0; p < fMetadata.fProxyCount; ++p) {
842 tmp += fViewCountPairs[p].fQuadCnt;
843 }
844 SkASSERT(tmp == fMetadata.fTotalQuadCount);
845#endif
846
847 return fMetadata.fTotalQuadCount;
848 }
849
850 int numChainedQuads() const {
851 int numChainedQuads = this->totNumQuads();
852
853 for (const GrOp* tmp = this->prevInChain(); tmp; tmp = tmp->prevInChain()) {
854 numChainedQuads += ((const TextureOp*)tmp)->totNumQuads();
855 }
856
857 for (const GrOp* tmp = this->nextInChain(); tmp; tmp = tmp->nextInChain()) {
858 numChainedQuads += ((const TextureOp*)tmp)->totNumQuads();
859 }
860
861 return numChainedQuads;
862 }
863
864 // onPrePrepareDraws may or may not have been called at this point
865 void onPrepareDraws(Target* target) override {
866 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
867
868 SkDEBUGCODE(this->validate();)
869
870 SkASSERT(!fDesc || fDesc->fPrePreparedVertices);
871
872 if (!fDesc) {
873 SkArenaAlloc* arena = target->allocator();
874 fDesc = arena->make<Desc>();
875 this->characterize(fDesc);
876 SkASSERT(!fDesc->fPrePreparedVertices);
877 }
878
879 size_t vertexSize = fDesc->fVertexSpec.vertexSize();
880
881 void* vdata = target->makeVertexSpace(vertexSize, fDesc->totalNumVertices(),
882 &fDesc->fVertexBuffer, &fDesc->fBaseVertex);
883 if (!vdata) {
884 SkDebugf("Could not allocate vertices\n");
885 return;
886 }
887
888 if (fDesc->fVertexSpec.needsIndexBuffer()) {
889 fDesc->fIndexBuffer = GrQuadPerEdgeAA::GetIndexBuffer(
890 target, fDesc->fVertexSpec.indexBufferOption());
891 if (!fDesc->fIndexBuffer) {
892 SkDebugf("Could not allocate indices\n");
893 return;
894 }
895 }
896
897 if (fDesc->fPrePreparedVertices) {
898 memcpy(vdata, fDesc->fPrePreparedVertices, fDesc->totalSizeInBytes());
899 } else {
900 FillInVertices(target->caps(), this, fDesc, (char*) vdata);
901 }
902 }
903
904 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
905 if (!fDesc->fVertexBuffer) {
906 return;
907 }
908
909 if (fDesc->fVertexSpec.needsIndexBuffer() && !fDesc->fIndexBuffer) {
910 return;
911 }
912
913 if (!fDesc->fProgramInfo) {
914 this->createProgramInfo(flushState);
915 SkASSERT(fDesc->fProgramInfo);
916 }
917
918 flushState->bindPipelineAndScissorClip(*fDesc->fProgramInfo, chainBounds);
919 flushState->bindBuffers(std::move(fDesc->fIndexBuffer), nullptr,
920 std::move(fDesc->fVertexBuffer));
921
922 int totQuadsSeen = 0;
923 SkDEBUGCODE(int numDraws = 0;)
924 for (const auto& op : ChainRange<TextureOp>(this)) {
925 for (unsigned p = 0; p < op.fMetadata.fProxyCount; ++p) {
926 const int quadCnt = op.fViewCountPairs[p].fQuadCnt;
927 SkASSERT(numDraws < fDesc->fNumProxies);
928 flushState->bindTextures(fDesc->fProgramInfo->primProc(),
929 *op.fViewCountPairs[p].fProxy,
930 fDesc->fProgramInfo->pipeline());
931 GrQuadPerEdgeAA::IssueDraw(flushState->caps(), flushState->opsRenderPass(),
932 fDesc->fVertexSpec, totQuadsSeen, quadCnt,
933 fDesc->totalNumVertices(), fDesc->fBaseVertex);
934 totQuadsSeen += quadCnt;
935 SkDEBUGCODE(++numDraws;)
936 }
937 }
938
939 SkASSERT(totQuadsSeen == fDesc->fNumTotalQuads);
940 SkASSERT(numDraws == fDesc->fNumProxies);
941 }
942
943 void propagateCoverageAAThroughoutChain() {
944 fMetadata.fAAType = static_cast<uint16_t>(GrAAType::kCoverage);
945
946 for (GrOp* tmp = this->prevInChain(); tmp; tmp = tmp->prevInChain()) {
947 TextureOp* tex = static_cast<TextureOp*>(tmp);
948 SkASSERT(tex->fMetadata.aaType() == GrAAType::kCoverage ||
949 tex->fMetadata.aaType() == GrAAType::kNone);
950 tex->fMetadata.fAAType = static_cast<uint16_t>(GrAAType::kCoverage);
951 }
952
953 for (GrOp* tmp = this->nextInChain(); tmp; tmp = tmp->nextInChain()) {
954 TextureOp* tex = static_cast<TextureOp*>(tmp);
955 SkASSERT(tex->fMetadata.aaType() == GrAAType::kCoverage ||
956 tex->fMetadata.aaType() == GrAAType::kNone);
957 tex->fMetadata.fAAType = static_cast<uint16_t>(GrAAType::kCoverage);
958 }
959 }
960
961 CombineResult onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*,
962 const GrCaps& caps) override {
963 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
964 auto* that = t->cast<TextureOp>();
965
966 SkDEBUGCODE(this->validate();)
967 SkDEBUGCODE(that->validate();)
968
969 if (fDesc || that->fDesc) {
970 // This should never happen (since only DDL recorded ops should be prePrepared)
971 // but, in any case, we should never combine ops that that been prePrepared
972 return CombineResult::kCannotCombine;
973 }
974
975 if (fMetadata.subset() != that->fMetadata.subset()) {
976 // It is technically possible to combine operations across subset modes, but performance
977 // testing suggests it's better to make more draw calls where some take advantage of
978 // the more optimal shader path without coordinate clamping.
979 return CombineResult::kCannotCombine;
980 }
981 if (!GrColorSpaceXform::Equals(fTextureColorSpaceXform.get(),
982 that->fTextureColorSpaceXform.get())) {
983 return CombineResult::kCannotCombine;
984 }
985
986 bool upgradeToCoverageAAOnMerge = false;
987 if (fMetadata.aaType() != that->fMetadata.aaType()) {
988 if (!CanUpgradeAAOnMerge(fMetadata.aaType(), that->fMetadata.aaType())) {
989 return CombineResult::kCannotCombine;
990 }
991 upgradeToCoverageAAOnMerge = true;
992 }
993
994 if (CombinedQuadCountWillOverflow(fMetadata.aaType(), upgradeToCoverageAAOnMerge,
995 this->numChainedQuads() + that->numChainedQuads())) {
996 return CombineResult::kCannotCombine;
997 }
998
999 if (fMetadata.saturate() != that->fMetadata.saturate()) {
1000 return CombineResult::kCannotCombine;
1001 }
1002 if (fMetadata.filter() != that->fMetadata.filter()) {
1003 return CombineResult::kCannotCombine;
1004 }
1005 if (fMetadata.mipmapMode() != that->fMetadata.mipmapMode()) {
1006 return CombineResult::kCannotCombine;
1007 }
1008 if (fMetadata.fSwizzle != that->fMetadata.fSwizzle) {
1009 return CombineResult::kCannotCombine;
1010 }
1011 const auto* thisProxy = fViewCountPairs[0].fProxy.get();
1012 const auto* thatProxy = that->fViewCountPairs[0].fProxy.get();
1013 if (fMetadata.fProxyCount > 1 || that->fMetadata.fProxyCount > 1 ||
1014 thisProxy != thatProxy) {
1015 // We can't merge across different proxies. Check if 'this' can be chained with 'that'.
1016 if (GrTextureProxy::ProxiesAreCompatibleAsDynamicState(thisProxy, thatProxy) &&
1017 caps.dynamicStateArrayGeometryProcessorTextureSupport() &&
1018 fMetadata.aaType() == that->fMetadata.aaType()) {
1019 // We only allow chaining when the aaTypes match bc otherwise the AA type
1020 // reported by the chain can be inconsistent. That is, since chaining doesn't
1021 // propagate revised AA information throughout the chain, the head of the chain
1022 // could have an AA setting of kNone while the chain as a whole could have a
1023 // setting of kCoverage. This inconsistency would then interfere with the validity
1024 // of the CombinedQuadCountWillOverflow calls.
1025 // This problem doesn't occur w/ merging bc we do propagate the AA information
1026 // (in propagateCoverageAAThroughoutChain) below.
1027 return CombineResult::kMayChain;
1028 }
1029 return CombineResult::kCannotCombine;
1030 }
1031
1032 fMetadata.fSubset |= that->fMetadata.fSubset;
1033 fMetadata.fColorType = std::max(fMetadata.fColorType, that->fMetadata.fColorType);
1034
1035 // Concatenate quad lists together
1036 fQuads.concat(that->fQuads);
1037 fViewCountPairs[0].fQuadCnt += that->fQuads.count();
1038 fMetadata.fTotalQuadCount += that->fQuads.count();
1039
1040 if (upgradeToCoverageAAOnMerge) {
1041 // This merger may be the start of a concatenation of two chains. When one
1042 // of the chains mutates its AA the other must follow suit or else the above AA
1043 // check may prevent later ops from chaining together. A specific example of this is
1044 // when chain2 is prepended onto chain1:
1045 // chain1 (that): opA (non-AA/mergeable) opB (non-AA/non-mergeable)
1046 // chain2 (this): opC (cov-AA/non-mergeable) opD (cov-AA/mergeable)
1047 // W/o this propagation, after opD & opA merge, opB and opC would say they couldn't
1048 // chain - which would stop the concatenation process.
1049 this->propagateCoverageAAThroughoutChain();
1050 that->propagateCoverageAAThroughoutChain();
1051 }
1052
1053 SkDEBUGCODE(this->validate();)
1054
1055 return CombineResult::kMerged;
1056 }
1057
1058#if GR_TEST_UTILS
1059 SkString onDumpInfo() const override {
1060 SkString str = SkStringPrintf("# draws: %d\n", fQuads.count());
1061 auto iter = fQuads.iterator();
1062 for (unsigned p = 0; p < fMetadata.fProxyCount; ++p) {
1063 str.appendf("Proxy ID: %d, Filter: %d, MM: %d\n",
1064 fViewCountPairs[p].fProxy->uniqueID().asUInt(),
1065 static_cast<int>(fMetadata.fFilter),
1066 static_cast<int>(fMetadata.fMipmapMode));
1067 int i = 0;
1068 while(i < fViewCountPairs[p].fQuadCnt && iter.next()) {
1069 const GrQuad* quad = iter.deviceQuad();
1070 GrQuad uv = iter.isLocalValid() ? *(iter.localQuad()) : GrQuad();
1071 const ColorSubsetAndAA& info = iter.metadata();
1072 str.appendf(
1073 "%d: Color: 0x%08x, Subset(%d): [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n"
1074 " UVs [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n"
1075 " Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n",
1076 i, info.fColor.toBytes_RGBA(), fMetadata.fSubset, info.fSubsetRect.fLeft,
1077 info.fSubsetRect.fTop, info.fSubsetRect.fRight, info.fSubsetRect.fBottom,
1078 quad->point(0).fX, quad->point(0).fY, quad->point(1).fX, quad->point(1).fY,
1079 quad->point(2).fX, quad->point(2).fY, quad->point(3).fX, quad->point(3).fY,
1080 uv.point(0).fX, uv.point(0).fY, uv.point(1).fX, uv.point(1).fY,
1081 uv.point(2).fX, uv.point(2).fY, uv.point(3).fX, uv.point(3).fY);
1082
1083 i++;
1084 }
1085 }
1086 return str;
1087 }
1088#endif
1089
1090 GrQuadBuffer<ColorSubsetAndAA> fQuads;
1091 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1092 // Most state of TextureOp is packed into these two field to minimize the op's size.
1093 // Historically, increasing the size of TextureOp has caused surprising perf regressions, so
1094 // consider/measure changes with care.
1095 Desc* fDesc;
1096 Metadata fMetadata;
1097
1098 // This field must go last. When allocating this op, we will allocate extra space to hold
1099 // additional ViewCountPairs immediately after the op's allocation so we can treat this
1100 // as an fProxyCnt-length array.
1101 ViewCountPair fViewCountPairs[1];
1102
1103 typedef GrMeshDrawOp INHERITED;
1104};
1105
1106} // anonymous namespace
1107
1108#if GR_TEST_UTILS
1109uint32_t GrTextureOp::ClassID() {
1110 return TextureOp::ClassID();
1111}
1112#endif
1113
1114std::unique_ptr<GrDrawOp> GrTextureOp::Make(GrRecordingContext* context,
1115 GrSurfaceProxyView proxyView,
1116 SkAlphaType alphaType,
1117 sk_sp<GrColorSpaceXform> textureXform,
1118 GrSamplerState::Filter filter,
1119 GrSamplerState::MipmapMode mm,
1120 const SkPMColor4f& color,
1121 Saturate saturate,
1122 SkBlendMode blendMode,
1123 GrAAType aaType,
1124 DrawQuad* quad,
1125 const SkRect* subset) {
1126 // Apply optimizations that are valid whether or not using GrTextureOp or GrFillRectOp
1127 if (subset && subset->contains(proxyView.proxy()->backingStoreBoundsRect())) {
1128 // No need for a shader-based subset if hardware clamping achieves the same effect
1129 subset = nullptr;
1130 }
1131
1132 if (filter != GrSamplerState::Filter::kNearest || mm != GrSamplerState::MipmapMode::kNone) {
1133 auto [mustFilter, mustMM] = filter_and_mm_have_effect(quad->fLocal, quad->fDevice);
1134 if (!mustFilter) {
1135 filter = GrSamplerState::Filter::kNearest;
1136 }
1137 if (!mustMM) {
1138 mm = GrSamplerState::MipmapMode::kNone;
1139 }
1140 }
1141
1142 if (blendMode == SkBlendMode::kSrcOver) {
1143 return TextureOp::Make(context, std::move(proxyView), std::move(textureXform), filter, mm,
1144 color, saturate, aaType, std::move(quad), subset);
1145 } else {
1146 // Emulate complex blending using GrFillRectOp
1147 GrPaint paint;
1148 paint.setColor4f(color);
1149 paint.setXPFactory(SkBlendMode_AsXPFactory(blendMode));
1150
1151 std::unique_ptr<GrFragmentProcessor> fp;
1152 if (subset) {
1153 const auto& caps = *context->priv().caps();
1154 SkRect localRect;
1155 if (quad->fLocal.asRect(&localRect)) {
1156 fp = GrTextureEffect::MakeSubset(std::move(proxyView), alphaType, SkMatrix::I(),
1157 filter, *subset, localRect, caps);
1158 } else {
1159 fp = GrTextureEffect::MakeSubset(std::move(proxyView), alphaType, SkMatrix::I(),
1160 filter, *subset, caps);
1161 }
1162 } else {
1163 fp = GrTextureEffect::Make(std::move(proxyView), alphaType, SkMatrix::I(), filter);
1164 }
1165 fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(textureXform));
1166 fp = GrBlendFragmentProcessor::Make(std::move(fp), nullptr, SkBlendMode::kModulate);
1167 if (saturate == GrTextureOp::Saturate::kYes) {
1168 fp = GrClampFragmentProcessor::Make(std::move(fp), /*clampToPremul=*/false);
1169 }
1170 paint.setColorFragmentProcessor(std::move(fp));
1171 return GrFillRectOp::Make(context, std::move(paint), aaType, quad);
1172 }
1173}
1174
1175// A helper class that assists in breaking up bulk API quad draws into manageable chunks.
1176class GrTextureOp::BatchSizeLimiter {
1177public:
1178 BatchSizeLimiter(GrRenderTargetContext* rtc,
1179 const GrClip* clip,
1180 GrRecordingContext* context,
1181 int numEntries,
1182 GrSamplerState::Filter filter,
1183 GrSamplerState::MipmapMode mm,
1184 GrTextureOp::Saturate saturate,
1185 SkCanvas::SrcRectConstraint constraint,
1186 const SkMatrix& viewMatrix,
1187 sk_sp<GrColorSpaceXform> textureColorSpaceXform)
1188 : fRTC(rtc)
1189 , fClip(clip)
1190 , fContext(context)
1191 , fFilter(filter)
1192 , fMipmapMode(mm)
1193 , fSaturate(saturate)
1194 , fConstraint(constraint)
1195 , fViewMatrix(viewMatrix)
1196 , fTextureColorSpaceXform(textureColorSpaceXform)
1197 , fNumLeft(numEntries) {}
1198
1199 void createOp(GrRenderTargetContext::TextureSetEntry set[],
1200 int clumpSize,
1201 GrAAType aaType) {
1202 int clumpProxyCount = proxy_run_count(&set[fNumClumped], clumpSize);
1203 std::unique_ptr<GrDrawOp> op = TextureOp::Make(fContext,
1204 &set[fNumClumped],
1205 clumpSize,
1206 clumpProxyCount,
1207 fFilter,
1208 fMipmapMode,
1209 fSaturate,
1210 aaType,
1211 fConstraint,
1212 fViewMatrix,
1213 fTextureColorSpaceXform);
1214 fRTC->addDrawOp(fClip, std::move(op));
1215
1216 fNumLeft -= clumpSize;
1217 fNumClumped += clumpSize;
1218 }
1219
1220 int numLeft() const { return fNumLeft; }
1221 int baseIndex() const { return fNumClumped; }
1222
1223private:
1224 GrRenderTargetContext* fRTC;
1225 const GrClip* fClip;
1226 GrRecordingContext* fContext;
1227 GrSamplerState::Filter fFilter;
1228 GrSamplerState::MipmapMode fMipmapMode;
1229 GrTextureOp::Saturate fSaturate;
1230 SkCanvas::SrcRectConstraint fConstraint;
1231 const SkMatrix& fViewMatrix;
1232 sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
1233
1234 int fNumLeft;
1235 int fNumClumped = 0; // also the offset for the start of the next clump
1236};
1237
1238// Greedily clump quad draws together until the index buffer limit is exceeded.
1239void GrTextureOp::AddTextureSetOps(GrRenderTargetContext* rtc,
1240 const GrClip* clip,
1241 GrRecordingContext* context,
1242 GrRenderTargetContext::TextureSetEntry set[],
1243 int cnt,
1244 int proxyRunCnt,
1245 GrSamplerState::Filter filter,
1246 GrSamplerState::MipmapMode mm,
1247 Saturate saturate,
1248 SkBlendMode blendMode,
1249 GrAAType aaType,
1250 SkCanvas::SrcRectConstraint constraint,
1251 const SkMatrix& viewMatrix,
1252 sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
1253 // Ensure that the index buffer limits are lower than the proxy and quad count limits of
1254 // the op's metadata so we don't need to worry about overflow.
1255 SkDEBUGCODE(TextureOp::ValidateResourceLimits();)
1256 SkASSERT(proxy_run_count(set, cnt) == proxyRunCnt);
1257
1258 // First check if we can support batches as a single op
1259 if (blendMode != SkBlendMode::kSrcOver ||
1260 !context->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
1261 // Append each entry as its own op; these may still be GrTextureOps if the blend mode is
1262 // src-over but the backend doesn't support dynamic state changes. Otherwise Make()
1263 // automatically creates the appropriate GrFillRectOp to emulate GrTextureOp.
1264 SkMatrix ctm;
1265 for (int i = 0; i < cnt; ++i) {
1266 ctm = viewMatrix;
1267 if (set[i].fPreViewMatrix) {
1268 ctm.preConcat(*set[i].fPreViewMatrix);
1269 }
1270
1271 DrawQuad quad;
1272 quad.fEdgeFlags = set[i].fAAFlags;
1273 if (set[i].fDstClipQuad) {
1274 quad.fDevice = GrQuad::MakeFromSkQuad(set[i].fDstClipQuad, ctm);
1275
1276 SkPoint srcPts[4];
1277 GrMapRectPoints(set[i].fDstRect, set[i].fSrcRect, set[i].fDstClipQuad, srcPts, 4);
1278 quad.fLocal = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
1279 } else {
1280 quad.fDevice = GrQuad::MakeFromRect(set[i].fDstRect, ctm);
1281 quad.fLocal = GrQuad(set[i].fSrcRect);
1282 }
1283
1284 const SkRect* subset = constraint == SkCanvas::kStrict_SrcRectConstraint
1285 ? &set[i].fSrcRect : nullptr;
1286
1287 auto op = Make(context, set[i].fProxyView, set[i].fSrcAlphaType, textureColorSpaceXform,
1288 filter, mm, set[i].fColor, saturate, blendMode, aaType, &quad, subset);
1289 rtc->addDrawOp(clip, std::move(op));
1290 }
1291 return;
1292 }
1293
1294 // Second check if we can always just make a single op and avoid the extra iteration
1295 // needed to clump things together.
1296 if (cnt <= std::min(GrResourceProvider::MaxNumNonAAQuads(),
1297 GrResourceProvider::MaxNumAAQuads())) {
1298 auto op = TextureOp::Make(context, set, cnt, proxyRunCnt, filter, mm, saturate, aaType,
1299 constraint, viewMatrix, std::move(textureColorSpaceXform));
1300 rtc->addDrawOp(clip, std::move(op));
1301 return;
1302 }
1303
1304 BatchSizeLimiter state(rtc, clip, context, cnt, filter, mm, saturate, constraint, viewMatrix,
1305 std::move(textureColorSpaceXform));
1306
1307 // kNone and kMSAA never get altered
1308 if (aaType == GrAAType::kNone || aaType == GrAAType::kMSAA) {
1309 // Clump these into series of MaxNumNonAAQuads-sized GrTextureOps
1310 while (state.numLeft() > 0) {
1311 int clumpSize = std::min(state.numLeft(), GrResourceProvider::MaxNumNonAAQuads());
1312
1313 state.createOp(set, clumpSize, aaType);
1314 }
1315 } else {
1316 // kCoverage can be downgraded to kNone. Note that the following is conservative. kCoverage
1317 // can also get downgraded to kNone if all the quads are on integer coordinates and
1318 // axis-aligned.
1319 SkASSERT(aaType == GrAAType::kCoverage);
1320
1321 while (state.numLeft() > 0) {
1322 GrAAType runningAA = GrAAType::kNone;
1323 bool clumped = false;
1324
1325 for (int i = 0; i < state.numLeft(); ++i) {
1326 int absIndex = state.baseIndex() + i;
1327
1328 if (set[absIndex].fAAFlags != GrQuadAAFlags::kNone ||
1329 runningAA == GrAAType::kCoverage) {
1330
1331 if (i >= GrResourceProvider::MaxNumAAQuads()) {
1332 // Here we either need to boost the AA type to kCoverage, but doing so with
1333 // all the accumulated quads would overflow, or we have a set of AA quads
1334 // that has just gotten too large. In either case, calve off the existing
1335 // quads as their own TextureOp.
1336 state.createOp(
1337 set,
1338 runningAA == GrAAType::kNone ? i : GrResourceProvider::MaxNumAAQuads(),
1339 runningAA); // maybe downgrading AA here
1340 clumped = true;
1341 break;
1342 }
1343
1344 runningAA = GrAAType::kCoverage;
1345 } else if (runningAA == GrAAType::kNone) {
1346
1347 if (i >= GrResourceProvider::MaxNumNonAAQuads()) {
1348 // Here we've found a consistent batch of non-AA quads that has gotten too
1349 // large. Calve it off as its own GrTextureOp.
1350 state.createOp(set, GrResourceProvider::MaxNumNonAAQuads(),
1351 GrAAType::kNone); // definitely downgrading AA here
1352 clumped = true;
1353 break;
1354 }
1355 }
1356 }
1357
1358 if (!clumped) {
1359 // We ran through the above loop w/o hitting a limit. Spit out this last clump of
1360 // quads and call it a day.
1361 state.createOp(set, state.numLeft(), runningAA); // maybe downgrading AA here
1362 }
1363 }
1364 }
1365}
1366
1367#if GR_TEST_UTILS
1368#include "include/gpu/GrRecordingContext.h"
1369#include "src/gpu/GrProxyProvider.h"
1370#include "src/gpu/GrRecordingContextPriv.h"
1371
1372GR_DRAW_OP_TEST_DEFINE(TextureOp) {
1373 SkISize dims;
1374 dims.fHeight = random->nextULessThan(90) + 10;
1375 dims.fWidth = random->nextULessThan(90) + 10;
1376 auto origin = random->nextBool() ? kTopLeft_GrSurfaceOrigin : kBottomLeft_GrSurfaceOrigin;
1377 GrMipmapped mipMapped = random->nextBool() ? GrMipmapped::kYes : GrMipmapped::kNo;
1378 SkBackingFit fit = SkBackingFit::kExact;
1379 if (mipMapped == GrMipmapped::kNo) {
1380 fit = random->nextBool() ? SkBackingFit::kApprox : SkBackingFit::kExact;
1381 }
1382 const GrBackendFormat format =
1383 context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888,
1384 GrRenderable::kNo);
1385 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
1386 sk_sp<GrTextureProxy> proxy = proxyProvider->createProxy(
1387 format, dims, GrRenderable::kNo, 1, mipMapped, fit, SkBudgeted::kNo, GrProtected::kNo,
1388 GrInternalSurfaceFlags::kNone);
1389
1390 SkRect rect = GrTest::TestRect(random);
1391 SkRect srcRect;
1392 srcRect.fLeft = random->nextRangeScalar(0.f, proxy->width() / 2.f);
1393 srcRect.fRight = random->nextRangeScalar(0.f, proxy->width()) + proxy->width() / 2.f;
1394 srcRect.fTop = random->nextRangeScalar(0.f, proxy->height() / 2.f);
1395 srcRect.fBottom = random->nextRangeScalar(0.f, proxy->height()) + proxy->height() / 2.f;
1396 SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random);
1397 SkPMColor4f color = SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU()));
1398 GrSamplerState::Filter filter = (GrSamplerState::Filter)random->nextULessThan(
1399 static_cast<uint32_t>(GrSamplerState::Filter::kLast) + 1);
1400 GrSamplerState::MipmapMode mm = GrSamplerState::MipmapMode::kNone;
1401 if (mipMapped == GrMipmapped::kYes) {
1402 mm = (GrSamplerState::MipmapMode)random->nextULessThan(
1403 static_cast<uint32_t>(GrSamplerState::MipmapMode::kLast) + 1);
1404 }
1405
1406 auto texXform = GrTest::TestColorXform(random);
1407 GrAAType aaType = GrAAType::kNone;
1408 if (random->nextBool()) {
1409 aaType = (numSamples > 1) ? GrAAType::kMSAA : GrAAType::kCoverage;
1410 }
1411 GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone;
1412 aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
1413 aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
1414 aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
1415 aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
1416 bool useSubset = random->nextBool();
1417 auto saturate = random->nextBool() ? GrTextureOp::Saturate::kYes : GrTextureOp::Saturate::kNo;
1418 GrSurfaceProxyView proxyView(
1419 std::move(proxy), origin,
1420 context->priv().caps()->getReadSwizzle(format, GrColorType::kRGBA_8888));
1421 auto alphaType = static_cast<SkAlphaType>(
1422 random->nextRangeU(kUnknown_SkAlphaType + 1, kLastEnum_SkAlphaType));
1423
1424 DrawQuad quad = {GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(srcRect), aaFlags};
1425 return GrTextureOp::Make(context, std::move(proxyView), alphaType, std::move(texXform), filter,
1426 mm, color, saturate, SkBlendMode::kSrcOver, aaType, &quad,
1427 useSubset ? &srcRect : nullptr);
1428}
1429
1430#endif
1431