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