/src/skia/src/gpu/ganesh/ops/FillRRectOp.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2018 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 "src/gpu/ganesh/ops/FillRRectOp.h" |
9 | | |
10 | | #include "include/gpu/GrRecordingContext.h" |
11 | | #include "src/base/SkVx.h" |
12 | | #include "src/core/SkRRectPriv.h" |
13 | | #include "src/gpu/BufferWriter.h" |
14 | | #include "src/gpu/KeyBuilder.h" |
15 | | #include "src/gpu/ganesh/GrCaps.h" |
16 | | #include "src/gpu/ganesh/GrGeometryProcessor.h" |
17 | | #include "src/gpu/ganesh/GrMemoryPool.h" |
18 | | #include "src/gpu/ganesh/GrOpFlushState.h" |
19 | | #include "src/gpu/ganesh/GrOpsRenderPass.h" |
20 | | #include "src/gpu/ganesh/GrProgramInfo.h" |
21 | | #include "src/gpu/ganesh/GrRecordingContextPriv.h" |
22 | | #include "src/gpu/ganesh/GrResourceProvider.h" |
23 | | #include "src/gpu/ganesh/geometry/GrShape.h" |
24 | | #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" |
25 | | #include "src/gpu/ganesh/glsl/GrGLSLVarying.h" |
26 | | #include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h" |
27 | | #include "src/gpu/ganesh/ops/GrMeshDrawOp.h" |
28 | | #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h" |
29 | | |
30 | | using namespace skia_private; |
31 | | |
32 | | namespace skgpu::ganesh::FillRRectOp { |
33 | | |
34 | | namespace { |
35 | | |
36 | | // Note: Just checking m.restStaysRect is not sufficient |
37 | 0 | bool skews_are_relevant(const SkMatrix& m) { |
38 | 0 | SkASSERT(!m.hasPerspective()); |
39 | |
|
40 | 0 | if (m[SkMatrix::kMSkewX] == 0.0f && m[SkMatrix::kMSkewY] == 0.0f) { |
41 | 0 | return false; |
42 | 0 | } |
43 | | |
44 | 0 | static constexpr float kTol = SK_ScalarNearlyZero; |
45 | 0 | float absScaleX = SkScalarAbs(m[SkMatrix::kMScaleX]); |
46 | 0 | float absSkewX = SkScalarAbs(m[SkMatrix::kMSkewX]); |
47 | 0 | float absScaleY = SkScalarAbs(m[SkMatrix::kMScaleY]); |
48 | 0 | float absSkewY = SkScalarAbs(m[SkMatrix::kMSkewY]); |
49 | | |
50 | | // The maximum absolute column sum norm of the upper left 2x2 |
51 | 0 | float norm = std::max(absScaleX + absSkewY, absSkewX + absScaleY); |
52 | |
|
53 | 0 | return absSkewX > kTol * norm || absSkewY > kTol * norm; |
54 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::skews_are_relevant(SkMatrix const&) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::skews_are_relevant(SkMatrix const&) |
55 | | |
56 | | class FillRRectOpImpl final : public GrMeshDrawOp { |
57 | | private: |
58 | | using Helper = GrSimpleMeshDrawOpHelper; |
59 | | |
60 | | public: |
61 | | DEFINE_OP_CLASS_ID |
62 | | |
63 | | struct LocalCoords { |
64 | | enum class Type : bool { kRect, kMatrix }; |
65 | | LocalCoords(const SkRect& localRect) |
66 | | : fType(Type::kRect) |
67 | 145 | , fRect(localRect) {} |
68 | | LocalCoords(const SkMatrix& localMatrix) |
69 | | : fType(Type::kMatrix) |
70 | 0 | , fMatrix(localMatrix) {} |
71 | | Type fType; |
72 | | union { |
73 | | SkRect fRect; |
74 | | SkMatrix fMatrix; |
75 | | }; |
76 | | }; |
77 | | |
78 | | static GrOp::Owner Make(GrRecordingContext*, |
79 | | SkArenaAlloc*, |
80 | | GrPaint&&, |
81 | | const SkMatrix& viewMatrix, |
82 | | const SkRRect&, |
83 | | const LocalCoords&, |
84 | | GrAA); |
85 | | |
86 | 0 | const char* name() const override { return "FillRRectOp"; } |
87 | | |
88 | 0 | FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
89 | | |
90 | | ClipResult clipToShape(skgpu::ganesh::SurfaceDrawContext*, |
91 | | SkClipOp, |
92 | | const SkMatrix& clipMatrix, |
93 | | const GrShape&, |
94 | | GrAA) override; |
95 | | |
96 | | GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override; |
97 | | CombineResult onCombineIfPossible(GrOp*, SkArenaAlloc*, const GrCaps&) override; |
98 | | |
99 | | #if defined(GR_TEST_UTILS) |
100 | | SkString onDumpInfo() const override; |
101 | | #endif |
102 | | |
103 | 0 | void visitProxies(const GrVisitProxyFunc& func) const override { |
104 | 0 | if (fProgramInfo) { |
105 | 0 | fProgramInfo->visitFPProxies(func); |
106 | 0 | } else { |
107 | 0 | fHelper.visitProxies(func); |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | | void onPrepareDraws(GrMeshDrawTarget*) override; |
112 | | |
113 | | void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; |
114 | | |
115 | | private: |
116 | | friend class ::GrSimpleMeshDrawOpHelper; // for access to ctor |
117 | | friend class ::GrOp; // for access to ctor |
118 | | |
119 | | enum class ProcessorFlags { |
120 | | kNone = 0, |
121 | | kUseHWDerivatives = 1 << 0, |
122 | | kHasLocalCoords = 1 << 1, |
123 | | kWideColor = 1 << 2, |
124 | | kMSAAEnabled = 1 << 3, |
125 | | kFakeNonAA = 1 << 4, |
126 | | }; |
127 | | constexpr static int kNumProcessorFlags = 5; |
128 | | |
129 | | GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ProcessorFlags); |
130 | | |
131 | | class Processor; |
132 | | |
133 | | FillRRectOpImpl(GrProcessorSet*, |
134 | | const SkPMColor4f& paintColor, |
135 | | SkArenaAlloc*, |
136 | | const SkMatrix& viewMatrix, |
137 | | const SkRRect&, |
138 | | const LocalCoords&, |
139 | | ProcessorFlags); |
140 | | |
141 | 0 | GrProgramInfo* programInfo() override { return fProgramInfo; } |
142 | | |
143 | | // Create a GrProgramInfo object in the provided arena |
144 | | void onCreateProgramInfo(const GrCaps*, |
145 | | SkArenaAlloc*, |
146 | | const GrSurfaceProxyView& writeView, |
147 | | bool usesMSAASurface, |
148 | | GrAppliedClip&&, |
149 | | const GrDstProxyView&, |
150 | | GrXferBarrierFlags renderPassXferBarriers, |
151 | | GrLoadOp colorLoadOp) override; |
152 | | |
153 | | Helper fHelper; |
154 | | ProcessorFlags fProcessorFlags; |
155 | | |
156 | | struct Instance { |
157 | | Instance(const SkMatrix& viewMatrix, |
158 | | const SkRRect& rrect, |
159 | | const LocalCoords& localCoords, |
160 | | const SkPMColor4f& color) |
161 | 0 | : fViewMatrix(viewMatrix), fRRect(rrect), fLocalCoords(localCoords), fColor(color) { |
162 | 0 | } |
163 | | SkMatrix fViewMatrix; |
164 | | SkRRect fRRect; |
165 | | LocalCoords fLocalCoords; |
166 | | SkPMColor4f fColor; |
167 | | Instance* fNext = nullptr; |
168 | | }; |
169 | | |
170 | | Instance* fHeadInstance; |
171 | | Instance** fTailInstance; |
172 | | int fInstanceCount = 1; |
173 | | |
174 | | sk_sp<const GrBuffer> fInstanceBuffer; |
175 | | sk_sp<const GrBuffer> fVertexBuffer; |
176 | | sk_sp<const GrBuffer> fIndexBuffer; |
177 | | int fBaseInstance = 0; |
178 | | |
179 | | // If this op is prePrepared the created programInfo will be stored here for use in |
180 | | // onExecute. In the prePrepared case it will have been stored in the record-time arena. |
181 | | GrProgramInfo* fProgramInfo = nullptr; |
182 | | }; |
183 | | |
184 | | GR_MAKE_BITFIELD_CLASS_OPS(FillRRectOpImpl::ProcessorFlags) |
185 | | |
186 | | // Hardware derivatives are not always accurate enough for highly elliptical corners. This method |
187 | | // checks to make sure the corners will still all look good if we use HW derivatives. |
188 | | bool can_use_hw_derivatives_with_coverage(const GrShaderCaps&, |
189 | | const SkMatrix&, |
190 | | const SkRRect&); |
191 | | |
192 | | GrOp::Owner FillRRectOpImpl::Make(GrRecordingContext* ctx, |
193 | | SkArenaAlloc* arena, |
194 | | GrPaint&& paint, |
195 | | const SkMatrix& viewMatrix, |
196 | | const SkRRect& rrect, |
197 | | const LocalCoords& localCoords, |
198 | 145 | GrAA aa) { |
199 | 145 | const GrCaps* caps = ctx->priv().caps(); |
200 | | |
201 | 145 | if (!caps->drawInstancedSupport()) { |
202 | 145 | return nullptr; |
203 | 145 | } |
204 | | |
205 | | // We transform into a normalized -1..+1 space to draw the round rect. If the boundaries are too |
206 | | // large, the math can overflow. The caller can fall back on path rendering if this is the case. |
207 | 0 | if (std::max(rrect.height(), rrect.width()) >= 1e6f) { |
208 | 0 | return nullptr; |
209 | 0 | } |
210 | | |
211 | 0 | ProcessorFlags flags = ProcessorFlags::kNone; |
212 | | // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we already |
213 | | // use HW derivatives. The only trick will be adjusting the AA outset to account for |
214 | | // perspective. (i.e., outset = 0.5 * z.) |
215 | 0 | if (viewMatrix.hasPerspective()) { |
216 | 0 | return nullptr; |
217 | 0 | } |
218 | 0 | if (can_use_hw_derivatives_with_coverage(*caps->shaderCaps(), viewMatrix, rrect)) { |
219 | | // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms in |
220 | | // coverage mode. We use them as long as the approximation will be accurate enough. |
221 | 0 | flags |= ProcessorFlags::kUseHWDerivatives; |
222 | 0 | } |
223 | 0 | if (aa == GrAA::kNo) { |
224 | 0 | flags |= ProcessorFlags::kFakeNonAA; |
225 | 0 | } |
226 | |
|
227 | 0 | return Helper::FactoryHelper<FillRRectOpImpl>(ctx, std::move(paint), arena, viewMatrix, rrect, |
228 | 0 | localCoords, flags); |
229 | 0 | } |
230 | | |
231 | | FillRRectOpImpl::FillRRectOpImpl(GrProcessorSet* processorSet, |
232 | | const SkPMColor4f& paintColor, |
233 | | SkArenaAlloc* arena, |
234 | | const SkMatrix& viewMatrix, |
235 | | const SkRRect& rrect, |
236 | | const LocalCoords& localCoords, |
237 | | ProcessorFlags processorFlags) |
238 | | : GrMeshDrawOp(ClassID()) |
239 | | , fHelper(processorSet, |
240 | | (processorFlags & ProcessorFlags::kFakeNonAA) |
241 | | ? GrAAType::kNone |
242 | | : GrAAType::kCoverage) // Use analytic AA even if the RT is MSAA. |
243 | | , fProcessorFlags(processorFlags & ~(ProcessorFlags::kHasLocalCoords | |
244 | | ProcessorFlags::kWideColor | |
245 | | ProcessorFlags::kMSAAEnabled)) |
246 | | , fHeadInstance(arena->make<Instance>(viewMatrix, rrect, localCoords, paintColor)) |
247 | 0 | , fTailInstance(&fHeadInstance->fNext) { |
248 | | // FillRRectOp::Make fails if there is perspective. |
249 | 0 | SkASSERT(!viewMatrix.hasPerspective()); |
250 | 0 | this->setBounds(viewMatrix.mapRect(rrect.getBounds()), |
251 | 0 | GrOp::HasAABloat(!(processorFlags & ProcessorFlags::kFakeNonAA)), |
252 | 0 | GrOp::IsHairline::kNo); |
253 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::FillRRectOpImpl(GrProcessorSet*, SkRGBA4f<(SkAlphaType)2> const&, SkArenaAlloc*, SkMatrix const&, SkRRect const&, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::LocalCoords const&, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::ProcessorFlags) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::FillRRectOpImpl(GrProcessorSet*, SkRGBA4f<(SkAlphaType)2> const&, SkArenaAlloc*, SkMatrix const&, SkRRect const&, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::LocalCoords const&, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::ProcessorFlags) |
254 | | |
255 | | GrDrawOp::ClipResult FillRRectOpImpl::clipToShape(skgpu::ganesh::SurfaceDrawContext* sdc, |
256 | | SkClipOp clipOp, |
257 | | const SkMatrix& clipMatrix, |
258 | | const GrShape& shape, |
259 | 0 | GrAA aa) { |
260 | 0 | SkASSERT(fInstanceCount == 1); // This needs to be called before combining. |
261 | 0 | SkASSERT(fHeadInstance->fNext == nullptr); |
262 | |
|
263 | 0 | if ((shape.isRect() || shape.isRRect()) && |
264 | 0 | clipOp == SkClipOp::kIntersect && |
265 | 0 | (aa == GrAA::kNo) == (fProcessorFlags & ProcessorFlags::kFakeNonAA)) { |
266 | | // The clip shape is a round rect. Attempt to map it to a round rect in "viewMatrix" space. |
267 | 0 | SkRRect clipRRect; |
268 | 0 | if (clipMatrix == fHeadInstance->fViewMatrix) { |
269 | 0 | if (shape.isRect()) { |
270 | 0 | clipRRect.setRect(shape.rect()); |
271 | 0 | } else { |
272 | 0 | clipRRect = shape.rrect(); |
273 | 0 | } |
274 | 0 | } else { |
275 | | // Find a matrix that maps from "clipMatrix" space to "viewMatrix" space. |
276 | 0 | SkASSERT(!fHeadInstance->fViewMatrix.hasPerspective()); |
277 | 0 | if (clipMatrix.hasPerspective()) { |
278 | 0 | return ClipResult::kFail; |
279 | 0 | } |
280 | 0 | SkMatrix clipToView; |
281 | 0 | if (!fHeadInstance->fViewMatrix.invert(&clipToView)) { |
282 | 0 | return ClipResult::kClippedOut; |
283 | 0 | } |
284 | 0 | clipToView.preConcat(clipMatrix); |
285 | 0 | SkASSERT(!clipToView.hasPerspective()); |
286 | |
|
287 | 0 | if (skews_are_relevant(clipToView)) { |
288 | | // A rect in "clipMatrix" space is not a rect in "viewMatrix" space. |
289 | 0 | return ClipResult::kFail; |
290 | 0 | } |
291 | 0 | clipToView.setSkewX(0); |
292 | 0 | clipToView.setSkewY(0); |
293 | 0 | SkASSERT(clipToView.rectStaysRect()); |
294 | |
|
295 | 0 | if (shape.isRect()) { |
296 | 0 | clipRRect.setRect(clipToView.mapRect(shape.rect())); |
297 | 0 | } else { |
298 | 0 | if (!shape.rrect().transform(clipToView, &clipRRect)) { |
299 | | // Transforming the rrect failed. This shouldn't generally happen except in |
300 | | // cases of fp32 overflow. |
301 | 0 | return ClipResult::kFail; |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | // Intersect our round rect with the clip shape. |
307 | 0 | SkRRect isectRRect; |
308 | 0 | if (fHeadInstance->fRRect.isRect() && clipRRect.isRect()) { |
309 | 0 | SkRect isectRect; |
310 | 0 | if (!isectRect.intersect(fHeadInstance->fRRect.rect(), clipRRect.rect())) { |
311 | 0 | return ClipResult::kClippedOut; |
312 | 0 | } |
313 | 0 | isectRRect.setRect(isectRect); |
314 | 0 | } else { |
315 | 0 | isectRRect = SkRRectPriv::ConservativeIntersect(fHeadInstance->fRRect, clipRRect); |
316 | 0 | if (isectRRect.isEmpty()) { |
317 | | // The round rects did not intersect at all or the intersection was too complicated |
318 | | // to compute quickly. |
319 | 0 | return ClipResult::kFail; |
320 | 0 | } |
321 | 0 | } |
322 | | |
323 | | // Don't apply the clip geometrically if it becomes subpixel, since then the hairline |
324 | | // rendering may outset beyond the original clip. |
325 | 0 | SkRect devISectBounds = fHeadInstance->fViewMatrix.mapRect(isectRRect.rect()); |
326 | 0 | if (devISectBounds.width() < 1.f || devISectBounds.height() < 1.f) { |
327 | 0 | return ClipResult::kFail; |
328 | 0 | } |
329 | | |
330 | 0 | if (fHeadInstance->fLocalCoords.fType == LocalCoords::Type::kRect) { |
331 | | // Update the local rect. |
332 | 0 | auto rect = sk_bit_cast<skvx::float4>(fHeadInstance->fRRect.rect()); |
333 | 0 | auto local = sk_bit_cast<skvx::float4>(fHeadInstance->fLocalCoords.fRect); |
334 | 0 | auto isect = sk_bit_cast<skvx::float4>(isectRRect.rect()); |
335 | 0 | auto rectToLocalSize = (local - skvx::shuffle<2,3,0,1>(local)) / |
336 | 0 | (rect - skvx::shuffle<2,3,0,1>(rect)); |
337 | 0 | auto localCoordsRect = (isect - rect) * rectToLocalSize + local; |
338 | 0 | fHeadInstance->fLocalCoords.fRect.setLTRB(localCoordsRect.x(), |
339 | 0 | localCoordsRect.y(), |
340 | 0 | localCoordsRect.z(), |
341 | 0 | localCoordsRect.w()); |
342 | 0 | } |
343 | | |
344 | | // Update the round rect. |
345 | 0 | fHeadInstance->fRRect = isectRRect; |
346 | 0 | return ClipResult::kClippedGeometrically; |
347 | 0 | } |
348 | | |
349 | 0 | return ClipResult::kFail; |
350 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::clipToShape(skgpu::ganesh::SurfaceDrawContext*, SkClipOp, SkMatrix const&, GrShape const&, GrAA) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::clipToShape(skgpu::ganesh::SurfaceDrawContext*, SkClipOp, SkMatrix const&, GrShape const&, GrAA) |
351 | | |
352 | | GrProcessorSet::Analysis FillRRectOpImpl::finalize(const GrCaps& caps, const GrAppliedClip* clip, |
353 | 0 | GrClampType clampType) { |
354 | 0 | SkASSERT(fInstanceCount == 1); |
355 | 0 | SkASSERT(fHeadInstance->fNext == nullptr); |
356 | |
|
357 | 0 | bool isWideColor; |
358 | 0 | auto analysis = fHelper.finalizeProcessors(caps, clip, clampType, |
359 | 0 | GrProcessorAnalysisCoverage::kSingleChannel, |
360 | 0 | &fHeadInstance->fColor, &isWideColor); |
361 | 0 | if (isWideColor) { |
362 | 0 | fProcessorFlags |= ProcessorFlags::kWideColor; |
363 | 0 | } |
364 | 0 | if (analysis.usesLocalCoords()) { |
365 | 0 | fProcessorFlags |= ProcessorFlags::kHasLocalCoords; |
366 | 0 | } |
367 | 0 | return analysis; |
368 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::finalize(GrCaps const&, GrAppliedClip const*, GrClampType) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::finalize(GrCaps const&, GrAppliedClip const*, GrClampType) |
369 | | |
370 | | GrOp::CombineResult FillRRectOpImpl::onCombineIfPossible(GrOp* op, |
371 | | SkArenaAlloc*, |
372 | 0 | const GrCaps& caps) { |
373 | 0 | auto that = op->cast<FillRRectOpImpl>(); |
374 | 0 | if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds()) || |
375 | 0 | fProcessorFlags != that->fProcessorFlags) { |
376 | 0 | return CombineResult::kCannotCombine; |
377 | 0 | } |
378 | | |
379 | 0 | *fTailInstance = that->fHeadInstance; |
380 | 0 | fTailInstance = that->fTailInstance; |
381 | 0 | fInstanceCount += that->fInstanceCount; |
382 | 0 | return CombineResult::kMerged; |
383 | 0 | } |
384 | | |
385 | | #if defined(GR_TEST_UTILS) |
386 | 0 | SkString FillRRectOpImpl::onDumpInfo() const { |
387 | 0 | SkString str = SkStringPrintf("# instances: %d\n", fInstanceCount); |
388 | 0 | str += fHelper.dumpInfo(); |
389 | 0 | int i = 0; |
390 | 0 | for (Instance* tmp = fHeadInstance; tmp; tmp = tmp->fNext, ++i) { |
391 | 0 | str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f] ", |
392 | 0 | i, tmp->fColor.fR, tmp->fColor.fG, tmp->fColor.fB, tmp->fColor.fA); |
393 | 0 | SkMatrix m = tmp->fViewMatrix; |
394 | 0 | str.appendf("ViewMatrix: [%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f] ", |
395 | 0 | m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); |
396 | 0 | SkRect r = tmp->fRRect.rect(); |
397 | 0 | str.appendf("Rect: [%f %f %f %f]\n", r.fLeft, r.fTop, r.fRight, r.fBottom); |
398 | 0 | } |
399 | 0 | return str; |
400 | 0 | } |
401 | | #endif |
402 | | |
403 | | class FillRRectOpImpl::Processor final : public GrGeometryProcessor { |
404 | | public: |
405 | 0 | static GrGeometryProcessor* Make(SkArenaAlloc* arena, GrAAType aaType, ProcessorFlags flags) { |
406 | 0 | return arena->make([&](void* ptr) { |
407 | 0 | return new (ptr) Processor(aaType, flags); |
408 | 0 | }); |
409 | 0 | } |
410 | | |
411 | 0 | const char* name() const override { return "FillRRectOp::Processor"; } |
412 | | |
413 | 0 | void addToKey(const GrShaderCaps& caps, KeyBuilder* b) const override { |
414 | 0 | b->addBits(kNumProcessorFlags, (uint32_t)fFlags, "flags"); |
415 | 0 | } |
416 | | |
417 | | std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override; |
418 | | |
419 | | private: |
420 | | class Impl; |
421 | | |
422 | | Processor(GrAAType aaType, ProcessorFlags flags) |
423 | | : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID) |
424 | 0 | , fFlags(flags) { |
425 | 0 | this->setVertexAttributesWithImplicitOffsets(kVertexAttribs, std::size(kVertexAttribs)); |
426 | |
|
427 | 0 | fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, SkSLType::kFloat4); |
428 | 0 | fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, SkSLType::kFloat4); |
429 | 0 | fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, SkSLType::kFloat4); |
430 | 0 | if (fFlags & ProcessorFlags::kHasLocalCoords) { |
431 | 0 | fInstanceAttribs.emplace_back("translate_and_localrotate", |
432 | 0 | kFloat4_GrVertexAttribType, |
433 | 0 | SkSLType::kFloat4); |
434 | 0 | fInstanceAttribs.emplace_back( |
435 | 0 | "localrect", kFloat4_GrVertexAttribType, SkSLType::kFloat4); |
436 | 0 | } else { |
437 | 0 | fInstanceAttribs.emplace_back("translate_and_localrotate", |
438 | 0 | kFloat2_GrVertexAttribType, |
439 | 0 | SkSLType::kFloat2); |
440 | 0 | } |
441 | 0 | fColorAttrib = &fInstanceAttribs.push_back( |
442 | 0 | MakeColorAttribute("color", (fFlags & ProcessorFlags::kWideColor))); |
443 | 0 | SkASSERT(fInstanceAttribs.size() <= kMaxInstanceAttribs); |
444 | 0 | this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.begin(), |
445 | 0 | fInstanceAttribs.size()); |
446 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::Processor::Processor(GrAAType, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::ProcessorFlags) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::Processor::Processor(GrAAType, skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::ProcessorFlags) |
447 | | |
448 | | inline static constexpr Attribute kVertexAttribs[] = { |
449 | | {"radii_selector", kFloat4_GrVertexAttribType, SkSLType::kFloat4}, |
450 | | {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, SkSLType::kFloat4}, |
451 | | // Coverage only. |
452 | | {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, SkSLType::kFloat4}}; |
453 | | |
454 | | const ProcessorFlags fFlags; |
455 | | |
456 | | constexpr static int kMaxInstanceAttribs = 6; |
457 | | STArray<kMaxInstanceAttribs, Attribute> fInstanceAttribs; |
458 | | const Attribute* fColorAttrib; |
459 | | }; |
460 | | |
461 | | // Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear |
462 | | // coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal |
463 | | // edges. The Vertex struct tells the shader where to place its vertex within a normalized |
464 | | // ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode. |
465 | | struct CoverageVertex { |
466 | | std::array<float, 4> fRadiiSelector; |
467 | | std::array<float, 2> fCorner; |
468 | | std::array<float, 2> fRadiusOutset; |
469 | | std::array<float, 2> fAABloatDirection; |
470 | | float fCoverage; |
471 | | float fIsLinearCoverage; |
472 | | }; |
473 | | |
474 | | // This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices |
475 | | // of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than |
476 | | // rectangles. |
477 | | static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2); |
478 | | |
479 | | static constexpr CoverageVertex kVertexData[] = { |
480 | | // Left inset edge. |
481 | | {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{+1,0}}, 1, 1}, |
482 | | {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{+1,0}}, 1, 1}, |
483 | | |
484 | | // Top inset edge. |
485 | | {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,+1}}, 1, 1}, |
486 | | {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,+1}}, 1, 1}, |
487 | | |
488 | | // Right inset edge. |
489 | | {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{-1,0}}, 1, 1}, |
490 | | {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{-1,0}}, 1, 1}, |
491 | | |
492 | | // Bottom inset edge. |
493 | | {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,-1}}, 1, 1}, |
494 | | {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,-1}}, 1, 1}, |
495 | | |
496 | | |
497 | | // Left outset edge. |
498 | | {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{-1,0}}, 0, 1}, |
499 | | {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{-1,0}}, 0, 1}, |
500 | | |
501 | | // Top outset edge. |
502 | | {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,-1}}, 0, 1}, |
503 | | {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,-1}}, 0, 1}, |
504 | | |
505 | | // Right outset edge. |
506 | | {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{+1,0}}, 0, 1}, |
507 | | {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{+1,0}}, 0, 1}, |
508 | | |
509 | | // Bottom outset edge. |
510 | | {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,+1}}, 0, 1}, |
511 | | {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,+1}}, 0, 1}, |
512 | | |
513 | | |
514 | | // Top-left corner. |
515 | | {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{-1, 0}}, 0, 0}, |
516 | | {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{+1, 0}}, 1, 0}, |
517 | | {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,+1}}, 1, 0}, |
518 | | {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,-1}}, 0, 0}, |
519 | | {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}, {{-1,-1}}, 0, 0}, |
520 | | {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}, {{-1,-1}}, 0, 0}, |
521 | | |
522 | | // Top-right corner. |
523 | | {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,-1}}, 0, 0}, |
524 | | {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,+1}}, 1, 0}, |
525 | | {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{-1, 0}}, 1, 0}, |
526 | | {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{+1, 0}}, 0, 0}, |
527 | | {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}, {{+1,-1}}, 0, 0}, |
528 | | {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}, {{+1,-1}}, 0, 0}, |
529 | | |
530 | | // Bottom-right corner. |
531 | | {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{+1, 0}}, 0, 0}, |
532 | | {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{-1, 0}}, 1, 0}, |
533 | | {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,-1}}, 1, 0}, |
534 | | {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,+1}}, 0, 0}, |
535 | | {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}, {{+1,+1}}, 0, 0}, |
536 | | {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}, {{+1,+1}}, 0, 0}, |
537 | | |
538 | | // Bottom-left corner. |
539 | | {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,+1}}, 0, 0}, |
540 | | {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,-1}}, 1, 0}, |
541 | | {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{+1, 0}}, 1, 0}, |
542 | | {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{-1, 0}}, 0, 0}, |
543 | | {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}, {{-1,+1}}, 0, 0}, |
544 | | {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}, {{-1,+1}}, 0, 0}}; |
545 | | |
546 | | SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey); |
547 | | |
548 | | static constexpr uint16_t kIndexData[] = { |
549 | | // Inset octagon (solid coverage). |
550 | | 0, 1, 7, |
551 | | 1, 2, 7, |
552 | | 7, 2, 6, |
553 | | 2, 3, 6, |
554 | | 6, 3, 5, |
555 | | 3, 4, 5, |
556 | | |
557 | | // AA borders (linear coverage). |
558 | | 0, 1, 8, 1, 9, 8, |
559 | | 2, 3, 10, 3, 11, 10, |
560 | | 4, 5, 12, 5, 13, 12, |
561 | | 6, 7, 14, 7, 15, 14, |
562 | | |
563 | | // Top-left arc. |
564 | | 16, 17, 21, |
565 | | 17, 21, 18, |
566 | | 21, 18, 20, |
567 | | 18, 20, 19, |
568 | | |
569 | | // Top-right arc. |
570 | | 22, 23, 27, |
571 | | 23, 27, 24, |
572 | | 27, 24, 26, |
573 | | 24, 26, 25, |
574 | | |
575 | | // Bottom-right arc. |
576 | | 28, 29, 33, |
577 | | 29, 33, 30, |
578 | | 33, 30, 32, |
579 | | 30, 32, 31, |
580 | | |
581 | | // Bottom-left arc. |
582 | | 34, 35, 39, |
583 | | 35, 39, 36, |
584 | | 39, 36, 38, |
585 | | 36, 38, 37}; |
586 | | |
587 | | SKGPU_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey); |
588 | | |
589 | 0 | void FillRRectOpImpl::onPrepareDraws(GrMeshDrawTarget* target) { |
590 | 0 | if (!fProgramInfo) { |
591 | 0 | this->createProgramInfo(target); |
592 | 0 | } |
593 | |
|
594 | 0 | size_t instanceStride = fProgramInfo->geomProc().instanceStride(); |
595 | |
|
596 | 0 | if (VertexWriter instanceWriter = target->makeVertexWriter(instanceStride, fInstanceCount, |
597 | 0 | &fInstanceBuffer, &fBaseInstance)) { |
598 | 0 | SkDEBUGCODE(auto end = instanceWriter.mark(instanceStride * fInstanceCount)); |
599 | 0 | for (Instance* i = fHeadInstance; i; i = i->fNext) { |
600 | 0 | auto [l, t, r, b] = i->fRRect.rect(); |
601 | | |
602 | | // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space. |
603 | 0 | SkMatrix m; |
604 | | // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b]. |
605 | 0 | m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2); |
606 | | // Map to device space. |
607 | 0 | m.postConcat(i->fViewMatrix); |
608 | | |
609 | | // Convert the radii to [-1, -1, +1, +1] space and write their attribs. |
610 | 0 | skvx::float4 radiiX, radiiY; |
611 | 0 | skvx::strided_load2(&SkRRectPriv::GetRadiiArray(i->fRRect)->fX, radiiX, radiiY); |
612 | 0 | radiiX *= 2 / (r - l); |
613 | 0 | radiiY *= 2 / (b - t); |
614 | |
|
615 | 0 | instanceWriter << radiiX << radiiY |
616 | 0 | << m.getScaleX() << m.getSkewX() << m.getSkewY() << m.getScaleY() |
617 | 0 | << m.getTranslateX() << m.getTranslateY(); |
618 | |
|
619 | 0 | if (fProcessorFlags & ProcessorFlags::kHasLocalCoords) { |
620 | 0 | if (i->fLocalCoords.fType == LocalCoords::Type::kRect) { |
621 | 0 | instanceWriter << 0.f << 0.f // localrotate |
622 | 0 | << i->fLocalCoords.fRect; // localrect |
623 | 0 | } else { |
624 | 0 | SkASSERT(i->fLocalCoords.fType == LocalCoords::Type::kMatrix); |
625 | 0 | const SkRect& bounds = i->fRRect.rect(); |
626 | 0 | const SkMatrix& localMatrix = i->fLocalCoords.fMatrix; |
627 | 0 | SkVector u = localMatrix.mapVector(bounds.right() - bounds.left(), 0); |
628 | 0 | SkVector v = localMatrix.mapVector(0, bounds.bottom() - bounds.top()); |
629 | 0 | SkPoint l0 = localMatrix.mapPoint({bounds.left(), bounds.top()}); |
630 | 0 | instanceWriter << v.x() << u.y() // localrotate |
631 | 0 | << l0 << (l0.x() + u.x()) << (l0.y() + v.y()); // localrect |
632 | 0 | } |
633 | 0 | } |
634 | |
|
635 | 0 | instanceWriter << VertexColor(i->fColor, fProcessorFlags & ProcessorFlags::kWideColor); |
636 | 0 | } |
637 | 0 | SkASSERT(instanceWriter.mark() == end); |
638 | 0 | } |
639 | |
|
640 | 0 | SKGPU_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey); |
641 | |
|
642 | 0 | fIndexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, |
643 | 0 | sizeof(kIndexData), |
644 | 0 | kIndexData, gIndexBufferKey); |
645 | |
|
646 | 0 | SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey); |
647 | |
|
648 | 0 | fVertexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, |
649 | 0 | sizeof(kVertexData), |
650 | 0 | kVertexData, |
651 | 0 | gVertexBufferKey); |
652 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::onPrepareDraws(GrMeshDrawTarget*) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::onPrepareDraws(GrMeshDrawTarget*) |
653 | | |
654 | | class FillRRectOpImpl::Processor::Impl : public ProgramImpl { |
655 | | public: |
656 | | void setData(const GrGLSLProgramDataManager&, |
657 | | const GrShaderCaps&, |
658 | 0 | const GrGeometryProcessor&) override {} |
659 | | |
660 | | private: |
661 | 0 | void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { |
662 | 0 | GrGLSLVertexBuilder* v = args.fVertBuilder; |
663 | 0 | GrGLSLFPFragmentBuilder* f = args.fFragBuilder; |
664 | |
|
665 | 0 | const auto& proc = args.fGeomProc.cast<Processor>(); |
666 | 0 | bool useHWDerivatives = (proc.fFlags & ProcessorFlags::kUseHWDerivatives); |
667 | |
|
668 | 0 | SkASSERT(proc.vertexStride() == sizeof(CoverageVertex)); |
669 | |
|
670 | 0 | GrGLSLVaryingHandler* varyings = args.fVaryingHandler; |
671 | 0 | varyings->emitAttributes(proc); |
672 | 0 | f->codeAppendf("half4 %s;", args.fOutputColor); |
673 | 0 | varyings->addPassThroughAttribute(proc.fColorAttrib->asShaderVar(), |
674 | 0 | args.fOutputColor, |
675 | 0 | GrGLSLVaryingHandler::Interpolation::kCanBeFlat); |
676 | | |
677 | | // Emit the vertex shader. |
678 | | // When MSAA is enabled, we need to make sure every sample gets lit up on pixels that have |
679 | | // fractional coverage. We do this by making the ramp wider. |
680 | 0 | v->codeAppendf("float aa_bloat_multiplier = %i;", |
681 | 0 | (proc.fFlags & ProcessorFlags::kMSAAEnabled) |
682 | 0 | ? 2 // Outset an entire pixel (2 radii). |
683 | 0 | : (!(proc.fFlags & ProcessorFlags::kFakeNonAA)) |
684 | 0 | ? 1 // Outset one half pixel (1 radius). |
685 | 0 | : 0); // No AA bloat. |
686 | | |
687 | | // Unpack vertex attribs. |
688 | 0 | v->codeAppend("float2 corner = corner_and_radius_outsets.xy;"); |
689 | 0 | v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;"); |
690 | 0 | v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;"); |
691 | 0 | v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;"); |
692 | | |
693 | | // Find the amount to bloat each edge for AA (in source space). |
694 | 0 | v->codeAppend("float2 pixellength = inversesqrt(" |
695 | 0 | "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));"); |
696 | 0 | v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;"); |
697 | 0 | v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + " |
698 | 0 | "abs(normalized_axis_dirs.zw));"); |
699 | 0 | v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;"); |
700 | | |
701 | | // Identify our radii. |
702 | 0 | v->codeAppend("float4 radii_and_neighbors = radii_selector" |
703 | 0 | "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);"); |
704 | 0 | v->codeAppend("float2 radii = radii_and_neighbors.xy;"); |
705 | 0 | v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;"); |
706 | |
|
707 | 0 | v->codeAppend("float coverage_multiplier = 1;"); |
708 | 0 | v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {"); |
709 | | // The rrect is more narrow than a half-pixel AA coverage ramp. We can't |
710 | | // draw as-is or else opposite AA borders will overlap. Instead, fudge the |
711 | | // size up to the width of a coverage ramp, and then reduce total coverage |
712 | | // to make the rect appear more thin. |
713 | 0 | v->codeAppend( "corner = max(abs(corner), aa_bloatradius) * sign(corner);"); |
714 | 0 | v->codeAppend( "coverage_multiplier = 1 / (max(aa_bloatradius.x, 1) * " |
715 | 0 | "max(aa_bloatradius.y, 1));"); |
716 | | // Set radii to zero to ensure we take the "linear coverage" codepath. |
717 | | // (The "coverage" variable only has effect in the linear codepath.) |
718 | 0 | v->codeAppend( "radii = float2(0);"); |
719 | 0 | v->codeAppend("}"); |
720 | | |
721 | | // Unpack coverage. |
722 | 0 | v->codeAppend("float coverage = aa_bloat_and_coverage.z;"); |
723 | 0 | if (proc.fFlags & ProcessorFlags::kMSAAEnabled) { |
724 | | // MSAA has a wider ramp that goes from -.5 to 1.5 instead of 0 to 1. |
725 | 0 | v->codeAppendf("coverage = (coverage - .5) * aa_bloat_multiplier + .5;"); |
726 | 0 | } |
727 | |
|
728 | 0 | v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.5))) {"); |
729 | | // The radii are very small. Demote this arc to a sharp 90 degree corner. |
730 | 0 | v->codeAppend( "radii = float2(0);"); |
731 | | // Convert to a standard picture frame for an AA rect instead of the round |
732 | | // rect geometry. |
733 | 0 | v->codeAppend( "aa_bloat_direction = sign(corner);"); |
734 | 0 | v->codeAppend( "if (coverage > .5) {"); // Are we an inset edge? |
735 | 0 | v->codeAppend( "aa_bloat_direction = -aa_bloat_direction;"); |
736 | 0 | v->codeAppend( "}"); |
737 | 0 | v->codeAppend( "is_linear_coverage = 1;"); |
738 | 0 | v->codeAppend("} else {"); |
739 | | // Don't let radii get smaller than a coverage ramp plus an extra half |
740 | | // pixel for MSAA. Always use the same amount so we don't pop when |
741 | | // switching between MSAA and coverage. |
742 | 0 | v->codeAppend( "radii = clamp(radii, pixellength * 1.5, 2 - pixellength * 1.5);"); |
743 | 0 | v->codeAppend( "neighbor_radii = clamp(neighbor_radii, pixellength * 1.5, " |
744 | 0 | "2 - pixellength * 1.5);"); |
745 | | // Don't let neighboring radii get closer together than 1/16 pixel. |
746 | 0 | v->codeAppend( "float2 spacing = 2 - radii - neighbor_radii;"); |
747 | 0 | v->codeAppend( "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));"); |
748 | 0 | v->codeAppend( "radii -= extra_pad * .5;"); |
749 | 0 | v->codeAppend("}"); |
750 | | |
751 | | // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in |
752 | | // normalized [-1,-1,+1,+1] space. |
753 | 0 | v->codeAppend("float2 aa_outset = " |
754 | 0 | "aa_bloat_direction * aa_bloatradius * aa_bloat_multiplier;"); |
755 | 0 | v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;"); |
756 | |
|
757 | 0 | v->codeAppend("if (coverage > .5) {"); // Are we an inset edge? |
758 | | // Don't allow the aa insets to overlap. i.e., Don't let them inset past |
759 | | // the center (x=y=0). Since we don't allow the rect to become thinner |
760 | | // than 1px, this should only happen when using MSAA, where we inset by an |
761 | | // entire pixel instead of half. |
762 | 0 | v->codeAppend( "if (aa_bloat_direction.x != 0 && vertexpos.x * corner.x < 0) {"); |
763 | 0 | v->codeAppend( "float backset = abs(vertexpos.x);"); |
764 | 0 | v->codeAppend( "vertexpos.x = 0;"); |
765 | 0 | v->codeAppend( "vertexpos.y += " |
766 | 0 | "backset * sign(corner.y) * pixellength.y/pixellength.x;"); |
767 | 0 | v->codeAppend( "coverage = (coverage - .5) * abs(corner.x) / " |
768 | 0 | "(abs(corner.x) + backset) + .5;"); |
769 | 0 | v->codeAppend( "}"); |
770 | 0 | v->codeAppend( "if (aa_bloat_direction.y != 0 && vertexpos.y * corner.y < 0) {"); |
771 | 0 | v->codeAppend( "float backset = abs(vertexpos.y);"); |
772 | 0 | v->codeAppend( "vertexpos.y = 0;"); |
773 | 0 | v->codeAppend( "vertexpos.x += " |
774 | 0 | "backset * sign(corner.x) * pixellength.x/pixellength.y;"); |
775 | 0 | v->codeAppend( "coverage = (coverage - .5) * abs(corner.y) / " |
776 | 0 | "(abs(corner.y) + backset) + .5;"); |
777 | 0 | v->codeAppend( "}"); |
778 | 0 | v->codeAppend("}"); |
779 | | |
780 | | // Transform to device space. |
781 | 0 | v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);"); |
782 | 0 | v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate_and_localrotate.xy;"); |
783 | 0 | gpArgs->fPositionVar.set(SkSLType::kFloat2, "devcoord"); |
784 | | |
785 | | // Output local coordinates. |
786 | 0 | if (proc.fFlags & ProcessorFlags::kHasLocalCoords) { |
787 | | // Do math in a way that preserves exact local coord boundaries when there is no local |
788 | | // rotate and vertexpos is on an exact shape boundary. |
789 | 0 | v->codeAppend("float2 T = vertexpos * .5 + .5;"); |
790 | 0 | v->codeAppend("float2 localcoord = localrect.xy * (1 - T) + " |
791 | 0 | "localrect.zw * T + " |
792 | 0 | "translate_and_localrotate.zw * T.yx;"); |
793 | 0 | gpArgs->fLocalCoordVar.set(SkSLType::kFloat2, "localcoord"); |
794 | 0 | } |
795 | | |
796 | | // Setup interpolants for coverage. |
797 | 0 | GrGLSLVarying arcCoord(useHWDerivatives ? SkSLType::kFloat2 : SkSLType::kFloat4); |
798 | 0 | varyings->addVarying("arccoord", &arcCoord); |
799 | 0 | v->codeAppend("if (0 != is_linear_coverage) {"); |
800 | | // We are a non-corner piece: Set x=0 to indicate built-in coverage, and |
801 | | // interpolate linear coverage across y. |
802 | 0 | v->codeAppendf( "%s.xy = float2(0, coverage * coverage_multiplier);", |
803 | 0 | arcCoord.vsOut()); |
804 | 0 | v->codeAppend("} else {"); |
805 | | // Find the normalized arc coordinates for our corner ellipse. |
806 | | // (i.e., the coordinate system where x^2 + y^2 == 1). |
807 | 0 | v->codeAppend( "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;"); |
808 | | // We are a corner piece: Interpolate the arc coordinates for coverage. |
809 | | // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0 |
810 | | // instructs the fragment shader to use linear coverage). |
811 | 0 | v->codeAppendf( "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut()); |
812 | 0 | if (!useHWDerivatives) { |
813 | | // The gradient is order-1: Interpolate it across arccoord.zw. |
814 | 0 | v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);"); |
815 | 0 | v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut()); |
816 | 0 | } |
817 | 0 | v->codeAppend("}"); |
818 | | |
819 | | // Emit the fragment shader. |
820 | 0 | f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn()); |
821 | 0 | f->codeAppendf("half coverage;"); |
822 | 0 | f->codeAppendf("if (0 == x_plus_1) {"); |
823 | 0 | f->codeAppendf( "coverage = half(y);"); // We are a non-arc pixel (linear coverage). |
824 | 0 | f->codeAppendf("} else {"); |
825 | 0 | f->codeAppendf( "float fn = x_plus_1 * (x_plus_1 - 2);"); // fn = (x+1)*(x-1) = x^2-1 |
826 | 0 | f->codeAppendf( "fn = fma(y,y, fn);"); // fn = x^2 + y^2 - 1 |
827 | 0 | if (useHWDerivatives) { |
828 | 0 | f->codeAppendf("float fnwidth = fwidth(fn);"); |
829 | 0 | } else { |
830 | | // The gradient is interpolated across arccoord.zw. |
831 | 0 | f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn()); |
832 | 0 | f->codeAppendf("float fnwidth = abs(gx) + abs(gy);"); |
833 | 0 | } |
834 | 0 | f->codeAppendf( "coverage = .5 - half(fn/fnwidth);"); |
835 | 0 | if (proc.fFlags & ProcessorFlags::kMSAAEnabled) { |
836 | | // MSAA uses ramps larger than 1px, so we need to clamp in both branches. |
837 | 0 | f->codeAppendf("}"); |
838 | 0 | } |
839 | 0 | f->codeAppendf("coverage = clamp(coverage, 0, 1);"); |
840 | 0 | if (!(proc.fFlags & ProcessorFlags::kMSAAEnabled)) { |
841 | | // When not using MSAA, we only need to clamp in the "arc" branch. |
842 | 0 | f->codeAppendf("}"); |
843 | 0 | } |
844 | 0 | if (proc.fFlags & ProcessorFlags::kFakeNonAA) { |
845 | 0 | f->codeAppendf("coverage = (coverage >= .5) ? 1 : 0;"); |
846 | 0 | } |
847 | 0 | f->codeAppendf("half4 %s = half4(coverage);", args.fOutputCoverage); |
848 | 0 | } Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::Processor::Impl::onEmitCode(GrGeometryProcessor::ProgramImpl::EmitArgs&, GrGeometryProcessor::ProgramImpl::GrGPArgs*) Unexecuted instantiation: FillRRectOp.cpp:skgpu::ganesh::FillRRectOp::(anonymous namespace)::FillRRectOpImpl::Processor::Impl::onEmitCode(GrGeometryProcessor::ProgramImpl::EmitArgs&, GrGeometryProcessor::ProgramImpl::GrGPArgs*) |
849 | | }; |
850 | | |
851 | | std::unique_ptr<GrGeometryProcessor::ProgramImpl> FillRRectOpImpl::Processor::makeProgramImpl( |
852 | 0 | const GrShaderCaps&) const { |
853 | 0 | return std::make_unique<Impl>(); |
854 | 0 | } |
855 | | |
856 | | void FillRRectOpImpl::onCreateProgramInfo(const GrCaps* caps, |
857 | | SkArenaAlloc* arena, |
858 | | const GrSurfaceProxyView& writeView, |
859 | | bool usesMSAASurface, |
860 | | GrAppliedClip&& appliedClip, |
861 | | const GrDstProxyView& dstProxyView, |
862 | | GrXferBarrierFlags renderPassXferBarriers, |
863 | 0 | GrLoadOp colorLoadOp) { |
864 | 0 | if (usesMSAASurface) { |
865 | 0 | fProcessorFlags |= ProcessorFlags::kMSAAEnabled; |
866 | 0 | } |
867 | 0 | GrGeometryProcessor* gp = Processor::Make(arena, fHelper.aaType(), fProcessorFlags); |
868 | 0 | fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface, |
869 | 0 | std::move(appliedClip), dstProxyView, gp, |
870 | 0 | GrPrimitiveType::kTriangles, renderPassXferBarriers, |
871 | 0 | colorLoadOp); |
872 | 0 | } |
873 | | |
874 | 0 | void FillRRectOpImpl::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { |
875 | 0 | if (!fInstanceBuffer || !fIndexBuffer || !fVertexBuffer) { |
876 | 0 | return; // Setup failed. |
877 | 0 | } |
878 | | |
879 | 0 | flushState->bindPipelineAndScissorClip(*fProgramInfo, this->bounds()); |
880 | 0 | flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); |
881 | 0 | flushState->bindBuffers(std::move(fIndexBuffer), std::move(fInstanceBuffer), |
882 | 0 | std::move(fVertexBuffer)); |
883 | 0 | flushState->drawIndexedInstanced(std::size(kIndexData), 0, fInstanceCount, fBaseInstance, 0); |
884 | 0 | } |
885 | | |
886 | | // Will the given corner look good if we use HW derivatives? |
887 | | bool can_use_hw_derivatives_with_coverage(const skvx::float2& devScale, |
888 | 0 | const skvx::float2& cornerRadii) { |
889 | 0 | skvx::float2 devRadii = devScale * cornerRadii; |
890 | 0 | if (devRadii[1] < devRadii[0]) { |
891 | 0 | devRadii = skvx::shuffle<1,0>(devRadii); |
892 | 0 | } |
893 | 0 | float minDevRadius = std::max(devRadii[0], 1.f); // Shader clamps radius at a minimum of 1. |
894 | | // Is the gradient smooth enough for this corner look ok if we use hardware derivatives? |
895 | | // This threshold was arrived at subjevtively on an NVIDIA chip. |
896 | 0 | return minDevRadius * minDevRadius * 5 > devRadii[1]; |
897 | 0 | } |
898 | | |
899 | | bool can_use_hw_derivatives_with_coverage(const skvx::float2& devScale, |
900 | 0 | const SkVector& cornerRadii) { |
901 | 0 | return can_use_hw_derivatives_with_coverage(devScale, skvx::float2::Load(&cornerRadii)); |
902 | 0 | } |
903 | | |
904 | | // Will the given round rect look good if we use HW derivatives? |
905 | | bool can_use_hw_derivatives_with_coverage(const GrShaderCaps& shaderCaps, |
906 | | const SkMatrix& viewMatrix, |
907 | 0 | const SkRRect& rrect) { |
908 | 0 | if (!shaderCaps.fShaderDerivativeSupport) { |
909 | 0 | return false; |
910 | 0 | } |
911 | | |
912 | 0 | auto x = skvx::float2(viewMatrix.getScaleX(), viewMatrix.getSkewX()); |
913 | 0 | auto y = skvx::float2(viewMatrix.getSkewY(), viewMatrix.getScaleY()); |
914 | 0 | skvx::float2 devScale = sqrt(x*x + y*y); |
915 | 0 | switch (rrect.getType()) { |
916 | 0 | case SkRRect::kEmpty_Type: |
917 | 0 | case SkRRect::kRect_Type: |
918 | 0 | return true; |
919 | | |
920 | 0 | case SkRRect::kOval_Type: |
921 | 0 | case SkRRect::kSimple_Type: |
922 | 0 | return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii()); |
923 | | |
924 | 0 | case SkRRect::kNinePatch_Type: { |
925 | 0 | skvx::float2 r0 = skvx::float2::Load(SkRRectPriv::GetRadiiArray(rrect)); |
926 | 0 | skvx::float2 r1 = skvx::float2::Load(SkRRectPriv::GetRadiiArray(rrect) + 2); |
927 | 0 | skvx::float2 minRadii = min(r0, r1); |
928 | 0 | skvx::float2 maxRadii = max(r0, r1); |
929 | 0 | return can_use_hw_derivatives_with_coverage(devScale, |
930 | 0 | skvx::float2(minRadii[0], maxRadii[1])) && |
931 | 0 | can_use_hw_derivatives_with_coverage(devScale, |
932 | 0 | skvx::float2(maxRadii[0], minRadii[1])); |
933 | 0 | } |
934 | | |
935 | 0 | case SkRRect::kComplex_Type: { |
936 | 0 | for (int i = 0; i < 4; ++i) { |
937 | 0 | auto corner = static_cast<SkRRect::Corner>(i); |
938 | 0 | if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) { |
939 | 0 | return false; |
940 | 0 | } |
941 | 0 | } |
942 | 0 | return true; |
943 | 0 | } |
944 | 0 | } |
945 | 0 | SK_ABORT("Invalid round rect type."); |
946 | 0 | } |
947 | | |
948 | | } // anonymous namespace |
949 | | |
950 | | GrOp::Owner Make(GrRecordingContext* ctx, |
951 | | SkArenaAlloc* arena, |
952 | | GrPaint&& paint, |
953 | | const SkMatrix& viewMatrix, |
954 | | const SkRRect& rrect, |
955 | | const SkRect& localRect, |
956 | 145 | GrAA aa) { |
957 | 145 | return FillRRectOpImpl::Make(ctx, arena, std::move(paint), viewMatrix, rrect, localRect, aa); |
958 | 145 | } |
959 | | |
960 | | GrOp::Owner Make(GrRecordingContext* ctx, |
961 | | SkArenaAlloc* arena, |
962 | | GrPaint&& paint, |
963 | | const SkMatrix& viewMatrix, |
964 | | const SkRRect& rrect, |
965 | | const SkMatrix& localMatrix, |
966 | 0 | GrAA aa) { |
967 | 0 | return FillRRectOpImpl::Make(ctx, arena, std::move(paint), viewMatrix, rrect, localMatrix, aa); |
968 | 0 | } |
969 | | |
970 | | } // namespace skgpu::ganesh::FillRRectOp |
971 | | |
972 | | #if defined(GR_TEST_UTILS) |
973 | | |
974 | | #include "src/gpu/ganesh/GrDrawOpTest.h" |
975 | | |
976 | 0 | GR_DRAW_OP_TEST_DEFINE(FillRRectOp) { |
977 | 0 | SkArenaAlloc arena(64 * sizeof(float)); |
978 | 0 | SkMatrix viewMatrix = GrTest::TestMatrix(random); |
979 | 0 | GrAA aa = GrAA(random->nextBool()); |
980 | |
|
981 | 0 | SkRect rect = GrTest::TestRect(random); |
982 | 0 | float w = rect.width(); |
983 | 0 | float h = rect.height(); |
984 | |
|
985 | 0 | SkRRect rrect; |
986 | | // TODO: test out other rrect configurations |
987 | 0 | rrect.setNinePatch(rect, w / 3.0f, h / 4.0f, w / 5.0f, h / 6.0); |
988 | |
|
989 | 0 | return skgpu::ganesh::FillRRectOp::Make( |
990 | 0 | context, &arena, std::move(paint), viewMatrix, rrect, rrect.rect(), aa); |
991 | 0 | } |
992 | | |
993 | | #endif |