/src/skia/src/gpu/ganesh/ops/OpsTask.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2019 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 | | #include "src/gpu/ganesh/ops/OpsTask.h" |
8 | | |
9 | | #include "include/core/SkSize.h" |
10 | | #include "include/core/SkString.h" |
11 | | #include "include/gpu/GpuTypes.h" |
12 | | #include "include/gpu/ganesh/GrRecordingContext.h" |
13 | | #include "include/private/base/SkPoint_impl.h" |
14 | | #include "src/base/SkArenaAlloc.h" |
15 | | #include "src/base/SkScopeExit.h" |
16 | | #include "src/core/SkRectPriv.h" |
17 | | #include "src/core/SkStringUtils.h" |
18 | | #include "src/core/SkTraceEvent.h" |
19 | | #include "src/gpu/ganesh/GrAppliedClip.h" |
20 | | #include "src/gpu/ganesh/GrAttachment.h" |
21 | | #include "src/gpu/ganesh/GrAuditTrail.h" |
22 | | #include "src/gpu/ganesh/GrCaps.h" |
23 | | #include "src/gpu/ganesh/GrGpu.h" |
24 | | #include "src/gpu/ganesh/GrNativeRect.h" |
25 | | #include "src/gpu/ganesh/GrOpFlushState.h" |
26 | | #include "src/gpu/ganesh/GrOpsRenderPass.h" |
27 | | #include "src/gpu/ganesh/GrRecordingContextPriv.h" |
28 | | #include "src/gpu/ganesh/GrRenderTarget.h" |
29 | | #include "src/gpu/ganesh/GrRenderTargetProxy.h" |
30 | | #include "src/gpu/ganesh/GrResourceAllocator.h" |
31 | | #include "src/gpu/ganesh/GrResourceProvider.h" |
32 | | #include "src/gpu/ganesh/GrSurfaceProxyView.h" |
33 | | #include "src/gpu/ganesh/GrTextureProxy.h" |
34 | | #include "src/gpu/ganesh/GrTextureResolveManager.h" |
35 | | #include "src/gpu/ganesh/geometry/GrRect.h" |
36 | | |
37 | | #include <algorithm> |
38 | | #include <cstddef> |
39 | | #include <cstdint> |
40 | | #include <functional> |
41 | | #include <memory> |
42 | | #include <utility> |
43 | | |
44 | | class GrDrawingManager; |
45 | | enum GrSurfaceOrigin : int; |
46 | | |
47 | | using namespace skia_private; |
48 | | |
49 | | //////////////////////////////////////////////////////////////////////////////// |
50 | | |
51 | | namespace { |
52 | | |
53 | | // Experimentally we have found that most combining occurs within the first 10 comparisons. |
54 | | static const int kMaxOpMergeDistance = 10; |
55 | | static const int kMaxOpChainDistance = 10; |
56 | | |
57 | | //////////////////////////////////////////////////////////////////////////////// |
58 | | |
59 | 179k | inline bool can_reorder(const SkRect& a, const SkRect& b) { return !GrRectsOverlap(a, b); } |
60 | | |
61 | | GrOpsRenderPass* create_render_pass(GrGpu* gpu, |
62 | | GrRenderTarget* rt, |
63 | | bool useMSAASurface, |
64 | | GrAttachment* stencil, |
65 | | GrSurfaceOrigin origin, |
66 | | const SkIRect& bounds, |
67 | | GrLoadOp colorLoadOp, |
68 | | const std::array<float, 4>& loadClearColor, |
69 | | GrLoadOp stencilLoadOp, |
70 | | GrStoreOp stencilStoreOp, |
71 | | const TArray<GrSurfaceProxy*, true>& sampledProxies, |
72 | 172k | GrXferBarrierFlags renderPassXferBarriers) { |
73 | 172k | const GrOpsRenderPass::LoadAndStoreInfo kColorLoadStoreInfo { |
74 | 172k | colorLoadOp, |
75 | 172k | GrStoreOp::kStore, |
76 | 172k | loadClearColor |
77 | 172k | }; |
78 | | |
79 | | // TODO: |
80 | | // We would like to (at this level) only ever clear & discard. We would need |
81 | | // to stop splitting up higher level OpsTasks for copyOps to achieve that. |
82 | | // Note: we would still need SB loads and stores but they would happen at a |
83 | | // lower level (inside the VK command buffer). |
84 | 172k | const GrOpsRenderPass::StencilLoadAndStoreInfo stencilLoadAndStoreInfo { |
85 | 172k | stencilLoadOp, |
86 | 172k | stencilStoreOp, |
87 | 172k | }; |
88 | | |
89 | 172k | return gpu->getOpsRenderPass(rt, useMSAASurface, stencil, origin, bounds, kColorLoadStoreInfo, |
90 | 172k | stencilLoadAndStoreInfo, sampledProxies, renderPassXferBarriers); |
91 | 172k | } |
92 | | |
93 | | } // anonymous namespace |
94 | | |
95 | | //////////////////////////////////////////////////////////////////////////////// |
96 | | |
97 | | namespace skgpu::ganesh { |
98 | | |
99 | | inline OpsTask::OpChain::List::List(GrOp::Owner op) |
100 | 432k | : fHead(std::move(op)), fTail(fHead.get()) { |
101 | 432k | this->validate(); |
102 | 432k | } |
103 | | |
104 | 12.7k | inline OpsTask::OpChain::List::List(List&& that) { *this = std::move(that); } |
105 | | |
106 | 12.9k | inline OpsTask::OpChain::List& OpsTask::OpChain::List::operator=(List&& that) { |
107 | 12.9k | fHead = std::move(that.fHead); |
108 | 12.9k | fTail = that.fTail; |
109 | 12.9k | that.fTail = nullptr; |
110 | 12.9k | this->validate(); |
111 | 12.9k | return *this; |
112 | 12.9k | } |
113 | | |
114 | 432k | inline GrOp::Owner OpsTask::OpChain::List::popHead() { |
115 | 432k | SkASSERT(fHead); |
116 | 432k | auto temp = fHead->cutChain(); |
117 | 432k | std::swap(temp, fHead); |
118 | 432k | if (!fHead) { |
119 | 432k | SkASSERT(fTail == temp.get()); |
120 | 432k | fTail = nullptr; |
121 | 432k | } |
122 | 432k | return temp; |
123 | 432k | } |
124 | | |
125 | 0 | inline GrOp::Owner OpsTask::OpChain::List::removeOp(GrOp* op) { |
126 | | #ifdef SK_DEBUG |
127 | | auto head = op; |
128 | 0 | while (head->prevInChain()) { head = head->prevInChain(); } |
129 | 0 | SkASSERT(head == fHead.get()); |
130 | | #endif |
131 | 0 | auto prev = op->prevInChain(); |
132 | 0 | if (!prev) { |
133 | 0 | SkASSERT(op == fHead.get()); |
134 | 0 | return this->popHead(); |
135 | 0 | } |
136 | 0 | auto temp = prev->cutChain(); |
137 | 0 | if (auto next = temp->cutChain()) { |
138 | 0 | prev->chainConcat(std::move(next)); |
139 | 0 | } else { |
140 | 0 | SkASSERT(fTail == op); |
141 | 0 | fTail = prev; |
142 | 0 | } |
143 | 0 | this->validate(); |
144 | 0 | return temp; |
145 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::removeOp(GrOp*) Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::removeOp(GrOp*) |
146 | | |
147 | 0 | inline void OpsTask::OpChain::List::pushHead(GrOp::Owner op) { |
148 | 0 | SkASSERT(op); |
149 | 0 | SkASSERT(op->isChainHead()); |
150 | 0 | SkASSERT(op->isChainTail()); |
151 | 0 | if (fHead) { |
152 | 0 | op->chainConcat(std::move(fHead)); |
153 | 0 | fHead = std::move(op); |
154 | 0 | } else { |
155 | 0 | fHead = std::move(op); |
156 | 0 | fTail = fHead.get(); |
157 | 0 | } |
158 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::pushHead(std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >) Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::pushHead(std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >) |
159 | | |
160 | 0 | inline void OpsTask::OpChain::List::pushTail(GrOp::Owner op) { |
161 | 0 | SkASSERT(op->isChainTail()); |
162 | 0 | fTail->chainConcat(std::move(op)); |
163 | 0 | fTail = fTail->nextInChain(); |
164 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::pushTail(std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >) Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::List::pushTail(std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >) |
165 | | |
166 | 445k | inline void OpsTask::OpChain::List::validate() const { |
167 | | #ifdef SK_DEBUG |
168 | | if (fHead) { |
169 | | SkASSERT(fTail); |
170 | | fHead->validateChain(fTail); |
171 | | } |
172 | | #endif |
173 | 445k | } |
174 | | |
175 | | //////////////////////////////////////////////////////////////////////////////// |
176 | | |
177 | | OpsTask::OpChain::OpChain(GrOp::Owner op, GrProcessorSet::Analysis processorAnalysis, |
178 | | GrAppliedClip* appliedClip, const GrDstProxyView* dstProxyView) |
179 | | : fList{std::move(op)} |
180 | | , fProcessorAnalysis(processorAnalysis) |
181 | 241k | , fAppliedClip(appliedClip) { |
182 | 241k | if (fProcessorAnalysis.requiresDstTexture()) { |
183 | 37.1k | SkASSERT(dstProxyView && dstProxyView->proxy()); |
184 | 37.1k | fDstProxyView = *dstProxyView; |
185 | 37.1k | } |
186 | 241k | fBounds = fList.head()->bounds(); |
187 | 241k | } |
188 | | |
189 | 255k | void OpsTask::OpChain::visitProxies(const GrVisitProxyFunc& func) const { |
190 | 255k | if (fList.empty()) { |
191 | 210 | return; |
192 | 210 | } |
193 | 255k | for (const auto& op : GrOp::ChainRange<>(fList.head())) { |
194 | 255k | op.visitProxies(func); |
195 | 255k | } |
196 | 255k | if (fDstProxyView.proxy()) { |
197 | 37.1k | func(fDstProxyView.proxy(), skgpu::Mipmapped::kNo); |
198 | 37.1k | } |
199 | 255k | if (fAppliedClip) { |
200 | 175k | fAppliedClip->visitProxies(func); |
201 | 175k | } |
202 | 255k | } |
203 | | |
204 | 241k | void OpsTask::OpChain::deleteOps() { |
205 | 483k | while (!fList.empty()) { |
206 | | // Since the value goes out of scope immediately, the GrOp::Owner deletes the op. |
207 | 241k | fList.popHead(); |
208 | 241k | } |
209 | 241k | } |
210 | | |
211 | | // Concatenates two op chains and attempts to merge ops across the chains. Assumes that we know that |
212 | | // the two chains are chainable. Returns the new chain. |
213 | | OpsTask::OpChain::List OpsTask::OpChain::DoConcat(List chainA, List chainB, const GrCaps& caps, |
214 | | SkArenaAlloc* opsTaskArena, |
215 | 0 | GrAuditTrail* auditTrail) { |
216 | | // We process ops in chain b from head to tail. We attempt to merge with nodes in a, starting |
217 | | // at chain a's tail and working toward the head. We produce one of the following outcomes: |
218 | | // 1) b's head is merged into an op in a. |
219 | | // 2) An op from chain a is merged into b's head. (In this case b's head gets processed again.) |
220 | | // 3) b's head is popped from chain a and added at the tail of a. |
221 | | // After result 3 we don't want to attempt to merge the next head of b with the new tail of a, |
222 | | // as we assume merges were already attempted when chain b was created. So we keep track of the |
223 | | // original tail of a and start our iteration of a there. We also track the bounds of the nodes |
224 | | // appended to chain a that will be skipped for bounds testing. If the original tail of a is |
225 | | // merged into an op in b (case 2) then we advance the "original tail" towards the head of a. |
226 | 0 | GrOp* origATail = chainA.tail(); |
227 | 0 | SkRect skipBounds = SkRectPriv::MakeLargestInverted(); |
228 | 0 | do { |
229 | 0 | int numMergeChecks = 0; |
230 | 0 | bool merged = false; |
231 | 0 | bool noSkip = (origATail == chainA.tail()); |
232 | 0 | SkASSERT(noSkip == (skipBounds == SkRectPriv::MakeLargestInverted())); |
233 | 0 | bool canBackwardMerge = noSkip || can_reorder(chainB.head()->bounds(), skipBounds); |
234 | 0 | SkRect forwardMergeBounds = skipBounds; |
235 | 0 | GrOp* a = origATail; |
236 | 0 | while (a) { |
237 | 0 | bool canForwardMerge = |
238 | 0 | (a == chainA.tail()) || can_reorder(a->bounds(), forwardMergeBounds); |
239 | 0 | if (canForwardMerge || canBackwardMerge) { |
240 | 0 | auto result = a->combineIfPossible(chainB.head(), opsTaskArena, caps); |
241 | 0 | SkASSERT(result != GrOp::CombineResult::kCannotCombine); |
242 | 0 | merged = (result == GrOp::CombineResult::kMerged); |
243 | 0 | GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n", |
244 | 0 | chainB.head()->name(), chainB.head()->uniqueID(), a->name(), |
245 | 0 | a->uniqueID()); |
246 | 0 | } |
247 | 0 | if (merged) { |
248 | 0 | GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, a, chainB.head()); |
249 | 0 | if (canBackwardMerge) { |
250 | | // The GrOp::Owner releases the op. |
251 | 0 | chainB.popHead(); |
252 | 0 | } else { |
253 | | // We merged the contents of b's head into a. We will replace b's head with a in |
254 | | // chain b. |
255 | 0 | SkASSERT(canForwardMerge); |
256 | 0 | if (a == origATail) { |
257 | 0 | origATail = a->prevInChain(); |
258 | 0 | } |
259 | 0 | GrOp::Owner detachedA = chainA.removeOp(a); |
260 | | // The GrOp::Owner releases the op. |
261 | 0 | chainB.popHead(); |
262 | 0 | chainB.pushHead(std::move(detachedA)); |
263 | 0 | if (chainA.empty()) { |
264 | | // We merged all the nodes in chain a to chain b. |
265 | 0 | return chainB; |
266 | 0 | } |
267 | 0 | } |
268 | 0 | break; |
269 | 0 | } else { |
270 | 0 | if (++numMergeChecks == kMaxOpMergeDistance) { |
271 | 0 | break; |
272 | 0 | } |
273 | 0 | forwardMergeBounds.joinNonEmptyArg(a->bounds()); |
274 | 0 | canBackwardMerge = |
275 | 0 | canBackwardMerge && can_reorder(chainB.head()->bounds(), a->bounds()); |
276 | 0 | a = a->prevInChain(); |
277 | 0 | } |
278 | 0 | } |
279 | | // If we weren't able to merge b's head then pop b's head from chain b and make it the new |
280 | | // tail of a. |
281 | 0 | if (!merged) { |
282 | 0 | chainA.pushTail(chainB.popHead()); |
283 | 0 | skipBounds.joinNonEmptyArg(chainA.tail()->bounds()); |
284 | 0 | } |
285 | 0 | } while (!chainB.empty()); |
286 | 0 | return chainA; |
287 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::DoConcat(skgpu::ganesh::OpsTask::OpChain::List, skgpu::ganesh::OpsTask::OpChain::List, GrCaps const&, SkArenaAlloc*, GrAuditTrail*) Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::DoConcat(skgpu::ganesh::OpsTask::OpChain::List, skgpu::ganesh::OpsTask::OpChain::List, GrCaps const&, SkArenaAlloc*, GrAuditTrail*) |
288 | | |
289 | | // Attempts to concatenate the given chain onto our own and merge ops across the chains. Returns |
290 | | // whether the operation succeeded. On success, the provided list will be returned empty. |
291 | | bool OpsTask::OpChain::tryConcat( |
292 | | List* list, GrProcessorSet::Analysis processorAnalysis, const GrDstProxyView& dstProxyView, |
293 | | const GrAppliedClip* appliedClip, const SkRect& bounds, const GrCaps& caps, |
294 | 280k | SkArenaAlloc* opsTaskArena, GrAuditTrail* auditTrail) { |
295 | 280k | SkASSERT(!fList.empty()); |
296 | 280k | SkASSERT(!list->empty()); |
297 | 280k | SkASSERT(fProcessorAnalysis.requiresDstTexture() == SkToBool(fDstProxyView.proxy())); |
298 | 280k | SkASSERT(processorAnalysis.requiresDstTexture() == SkToBool(dstProxyView.proxy())); |
299 | | // All returns use explicit tuple constructor rather than {a, b} to work around old GCC bug. |
300 | 280k | if (fList.head()->classID() != list->head()->classID() || |
301 | 280k | SkToBool(fAppliedClip) != SkToBool(appliedClip) || |
302 | 280k | (fAppliedClip && *fAppliedClip != *appliedClip) || |
303 | 280k | (fProcessorAnalysis.requiresNonOverlappingDraws() != |
304 | 212k | processorAnalysis.requiresNonOverlappingDraws()) || |
305 | 280k | (fProcessorAnalysis.requiresNonOverlappingDraws() && |
306 | | // Non-overlaping draws are only required when Ganesh will either insert a barrier, |
307 | | // or read back a new dst texture between draws. In either case, we can neither |
308 | | // chain nor combine overlapping Ops. |
309 | 210k | GrRectsTouchOrOverlap(fBounds, bounds)) || |
310 | 280k | (fProcessorAnalysis.requiresDstTexture() != processorAnalysis.requiresDstTexture()) || |
311 | 280k | (fProcessorAnalysis.requiresDstTexture() && fDstProxyView != dstProxyView)) { |
312 | 69.2k | return false; |
313 | 69.2k | } |
314 | | |
315 | 0 | SkDEBUGCODE(bool first = true;) |
316 | 210k | do { |
317 | 210k | switch (fList.tail()->combineIfPossible(list->head(), opsTaskArena, caps)) |
318 | 210k | { |
319 | 110k | case GrOp::CombineResult::kCannotCombine: |
320 | | // If an op supports chaining then it is required that chaining is transitive and |
321 | | // that if any two ops in two different chains can merge then the two chains |
322 | | // may also be chained together. Thus, we should only hit this on the first |
323 | | // iteration. |
324 | 110k | SkASSERT(first); |
325 | 110k | return false; |
326 | 0 | case GrOp::CombineResult::kMayChain: |
327 | 0 | fList = DoConcat(std::move(fList), std::exchange(*list, List()), caps, opsTaskArena, |
328 | 0 | auditTrail); |
329 | | // The above exchange cleared out 'list'. The list needs to be empty now for the |
330 | | // loop to terminate. |
331 | 0 | SkASSERT(list->empty()); |
332 | 0 | break; |
333 | 100k | case GrOp::CombineResult::kMerged: { |
334 | 100k | GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n", |
335 | 100k | list->tail()->name(), list->tail()->uniqueID(), list->head()->name(), |
336 | 100k | list->head()->uniqueID()); |
337 | 100k | GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, fList.tail(), list->head()); |
338 | | // The GrOp::Owner releases the op. |
339 | 100k | list->popHead(); |
340 | 100k | break; |
341 | 0 | } |
342 | 210k | } |
343 | 100k | SkDEBUGCODE(first = false); |
344 | 100k | } while (!list->empty()); |
345 | | |
346 | | // The new ops were successfully merged and/or chained onto our own. |
347 | 100k | fBounds.joinPossiblyEmptyRect(bounds); |
348 | 100k | return true; |
349 | 210k | } skgpu::ganesh::OpsTask::OpChain::tryConcat(skgpu::ganesh::OpsTask::OpChain::List*, GrProcessorSet::Analysis, GrDstProxyView const&, GrAppliedClip const*, SkRect const&, GrCaps const&, SkArenaAlloc*, GrAuditTrail*) Line | Count | Source | 294 | 280k | SkArenaAlloc* opsTaskArena, GrAuditTrail* auditTrail) { | 295 | 280k | SkASSERT(!fList.empty()); | 296 | 280k | SkASSERT(!list->empty()); | 297 | 280k | SkASSERT(fProcessorAnalysis.requiresDstTexture() == SkToBool(fDstProxyView.proxy())); | 298 | 280k | SkASSERT(processorAnalysis.requiresDstTexture() == SkToBool(dstProxyView.proxy())); | 299 | | // All returns use explicit tuple constructor rather than {a, b} to work around old GCC bug. | 300 | 280k | if (fList.head()->classID() != list->head()->classID() || | 301 | 280k | SkToBool(fAppliedClip) != SkToBool(appliedClip) || | 302 | 280k | (fAppliedClip && *fAppliedClip != *appliedClip) || | 303 | 280k | (fProcessorAnalysis.requiresNonOverlappingDraws() != | 304 | 212k | processorAnalysis.requiresNonOverlappingDraws()) || | 305 | 280k | (fProcessorAnalysis.requiresNonOverlappingDraws() && | 306 | | // Non-overlaping draws are only required when Ganesh will either insert a barrier, | 307 | | // or read back a new dst texture between draws. In either case, we can neither | 308 | | // chain nor combine overlapping Ops. | 309 | 210k | GrRectsTouchOrOverlap(fBounds, bounds)) || | 310 | 280k | (fProcessorAnalysis.requiresDstTexture() != processorAnalysis.requiresDstTexture()) || | 311 | 280k | (fProcessorAnalysis.requiresDstTexture() && fDstProxyView != dstProxyView)) { | 312 | 69.2k | return false; | 313 | 69.2k | } | 314 | | | 315 | 210k | SkDEBUGCODE(bool first = true;) | 316 | 210k | do { | 317 | 210k | switch (fList.tail()->combineIfPossible(list->head(), opsTaskArena, caps)) | 318 | 210k | { | 319 | 110k | case GrOp::CombineResult::kCannotCombine: | 320 | | // If an op supports chaining then it is required that chaining is transitive and | 321 | | // that if any two ops in two different chains can merge then the two chains | 322 | | // may also be chained together. Thus, we should only hit this on the first | 323 | | // iteration. | 324 | 110k | SkASSERT(first); | 325 | 110k | return false; | 326 | 0 | case GrOp::CombineResult::kMayChain: | 327 | 0 | fList = DoConcat(std::move(fList), std::exchange(*list, List()), caps, opsTaskArena, | 328 | 0 | auditTrail); | 329 | | // The above exchange cleared out 'list'. The list needs to be empty now for the | 330 | | // loop to terminate. | 331 | 0 | SkASSERT(list->empty()); | 332 | 0 | break; | 333 | 100k | case GrOp::CombineResult::kMerged: { | 334 | 100k | GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n", | 335 | 100k | list->tail()->name(), list->tail()->uniqueID(), list->head()->name(), | 336 | 100k | list->head()->uniqueID()); | 337 | 100k | GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, fList.tail(), list->head()); | 338 | | // The GrOp::Owner releases the op. | 339 | 100k | list->popHead(); | 340 | 100k | break; | 341 | 0 | } | 342 | 210k | } | 343 | 100k | SkDEBUGCODE(first = false); | 344 | 100k | } while (!list->empty()); | 345 | | | 346 | | // The new ops were successfully merged and/or chained onto our own. | 347 | 100k | fBounds.joinPossiblyEmptyRect(bounds); | 348 | 100k | return true; | 349 | 210k | } |
Unexecuted instantiation: skgpu::ganesh::OpsTask::OpChain::tryConcat(skgpu::ganesh::OpsTask::OpChain::List*, GrProcessorSet::Analysis, GrDstProxyView const&, GrAppliedClip const*, SkRect const&, GrCaps const&, SkArenaAlloc*, GrAuditTrail*) |
350 | | |
351 | | bool OpsTask::OpChain::prependChain(OpChain* that, const GrCaps& caps, SkArenaAlloc* opsTaskArena, |
352 | 89.0k | GrAuditTrail* auditTrail) { |
353 | 89.0k | if (!that->tryConcat(&fList, fProcessorAnalysis, fDstProxyView, fAppliedClip, fBounds, caps, |
354 | 89.0k | opsTaskArena, auditTrail)) { |
355 | 88.8k | this->validate(); |
356 | | // append failed |
357 | 88.8k | return false; |
358 | 88.8k | } |
359 | | |
360 | | // 'that' owns the combined chain. Move it into 'this'. |
361 | 210 | SkASSERT(fList.empty()); |
362 | 210 | fList = std::move(that->fList); |
363 | 210 | fBounds = that->fBounds; |
364 | | |
365 | 210 | that->fDstProxyView.setProxyView({}); |
366 | 210 | if (that->fAppliedClip && that->fAppliedClip->hasCoverageFragmentProcessor()) { |
367 | | // Obliterates the processor. |
368 | 13 | that->fAppliedClip->detachCoverageFragmentProcessor(); |
369 | 13 | } |
370 | 210 | this->validate(); |
371 | 210 | return true; |
372 | 89.0k | } |
373 | | |
374 | | GrOp::Owner OpsTask::OpChain::appendOp( |
375 | | GrOp::Owner op, GrProcessorSet::Analysis processorAnalysis, |
376 | | const GrDstProxyView* dstProxyView, const GrAppliedClip* appliedClip, const GrCaps& caps, |
377 | 191k | SkArenaAlloc* opsTaskArena, GrAuditTrail* auditTrail) { |
378 | 191k | const GrDstProxyView noDstProxyView; |
379 | 191k | if (!dstProxyView) { |
380 | 5.09k | dstProxyView = &noDstProxyView; |
381 | 5.09k | } |
382 | 191k | SkASSERT(op->isChainHead() && op->isChainTail()); |
383 | 191k | SkRect opBounds = op->bounds(); |
384 | 191k | List chain(std::move(op)); |
385 | 191k | if (!this->tryConcat(&chain, processorAnalysis, *dstProxyView, appliedClip, opBounds, caps, |
386 | 191k | opsTaskArena, auditTrail)) { |
387 | | // append failed, give the op back to the caller. |
388 | 90.5k | this->validate(); |
389 | 90.5k | return chain.popHead(); |
390 | 90.5k | } |
391 | | |
392 | 100k | SkASSERT(chain.empty()); |
393 | 100k | this->validate(); |
394 | 100k | return nullptr; |
395 | 191k | } |
396 | | |
397 | 280k | inline void OpsTask::OpChain::validate() const { |
398 | | #ifdef SK_DEBUG |
399 | | fList.validate(); |
400 | | for (const auto& op : GrOp::ChainRange<>(fList.head())) { |
401 | | // Not using SkRect::contains because we allow empty rects. |
402 | | SkASSERT(fBounds.fLeft <= op.bounds().fLeft && fBounds.fTop <= op.bounds().fTop && |
403 | | fBounds.fRight >= op.bounds().fRight && fBounds.fBottom >= op.bounds().fBottom); |
404 | | } |
405 | | #endif |
406 | 280k | } |
407 | | |
408 | | //////////////////////////////////////////////////////////////////////////////// |
409 | | |
410 | | OpsTask::OpsTask(GrDrawingManager* drawingMgr, |
411 | | GrSurfaceProxyView view, |
412 | | GrAuditTrail* auditTrail, |
413 | | sk_sp<GrArenas> arenas) |
414 | | : GrRenderTask() |
415 | | , fAuditTrail(auditTrail) |
416 | | , fUsesMSAASurface(view.asRenderTargetProxy()->numSamples() > 1) |
417 | | , fTargetSwizzle(view.swizzle()) |
418 | | , fTargetOrigin(view.origin()) |
419 | | , fArenas{std::move(arenas)} |
420 | 195k | SkDEBUGCODE(, fNumClips(0)) { |
421 | 195k | this->addTarget(drawingMgr, view.detachProxy()); |
422 | 195k | } |
423 | | |
424 | 283k | void OpsTask::deleteOps() { |
425 | 283k | for (auto& chain : fOpChains) { |
426 | 241k | chain.deleteOps(); |
427 | 241k | } |
428 | 283k | fOpChains.clear(); |
429 | 283k | } |
430 | | |
431 | 195k | OpsTask::~OpsTask() { |
432 | 195k | this->deleteOps(); |
433 | 195k | } |
434 | | |
435 | | void OpsTask::addOp(GrDrawingManager* drawingMgr, GrOp::Owner op, |
436 | 4.98k | GrTextureResolveManager textureResolveManager, const GrCaps& caps) { |
437 | 4.98k | auto addDependency = [&](GrSurfaceProxy* p, skgpu::Mipmapped mipmapped) { |
438 | 0 | this->addDependency(drawingMgr, p, mipmapped, textureResolveManager, caps); |
439 | 0 | }; |
440 | | |
441 | 4.98k | op->visitProxies(addDependency); |
442 | | |
443 | 4.98k | this->recordOp(std::move(op), false/*usesMSAA*/, GrProcessorSet::EmptySetAnalysis(), nullptr, |
444 | 4.98k | nullptr, caps); |
445 | 4.98k | } |
446 | | |
447 | | void OpsTask::addDrawOp(GrDrawingManager* drawingMgr, GrOp::Owner op, bool usesMSAA, |
448 | | const GrProcessorSet::Analysis& processorAnalysis, GrAppliedClip&& clip, |
449 | | const GrDstProxyView& dstProxyView, |
450 | 337k | GrTextureResolveManager textureResolveManager, const GrCaps& caps) { |
451 | 370k | auto addDependency = [&](GrSurfaceProxy* p, skgpu::Mipmapped mipmapped) { |
452 | 370k | this->addSampledTexture(p); |
453 | 370k | this->addDependency(drawingMgr, p, mipmapped, textureResolveManager, caps); |
454 | 370k | }; |
455 | | |
456 | 337k | op->visitProxies(addDependency); |
457 | 337k | clip.visitProxies(addDependency); |
458 | 337k | if (dstProxyView.proxy()) { |
459 | 37.1k | if (!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment)) { |
460 | 37.1k | this->addSampledTexture(dstProxyView.proxy()); |
461 | 37.1k | } |
462 | 37.1k | if (dstProxyView.dstSampleFlags() & GrDstSampleFlags::kRequiresTextureBarrier) { |
463 | 0 | fRenderPassXferBarriers |= GrXferBarrierFlags::kTexture; |
464 | 0 | } |
465 | 37.1k | addDependency(dstProxyView.proxy(), skgpu::Mipmapped::kNo); |
466 | 37.1k | SkASSERT(!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment) || |
467 | 37.1k | dstProxyView.offset().isZero()); |
468 | 37.1k | } |
469 | | |
470 | 337k | if (processorAnalysis.usesNonCoherentHWBlending()) { |
471 | 0 | fRenderPassXferBarriers |= GrXferBarrierFlags::kBlend; |
472 | 0 | } |
473 | | |
474 | 337k | this->recordOp(std::move(op), usesMSAA, processorAnalysis, clip.doesClip() ? &clip : nullptr, |
475 | 337k | &dstProxyView, caps); |
476 | 337k | } skgpu::ganesh::OpsTask::addDrawOp(GrDrawingManager*, std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >, bool, GrProcessorSet::Analysis const&, GrAppliedClip&&, GrDstProxyView const&, GrTextureResolveManager, GrCaps const&) Line | Count | Source | 450 | 337k | GrTextureResolveManager textureResolveManager, const GrCaps& caps) { | 451 | 337k | auto addDependency = [&](GrSurfaceProxy* p, skgpu::Mipmapped mipmapped) { | 452 | 337k | this->addSampledTexture(p); | 453 | 337k | this->addDependency(drawingMgr, p, mipmapped, textureResolveManager, caps); | 454 | 337k | }; | 455 | | | 456 | 337k | op->visitProxies(addDependency); | 457 | 337k | clip.visitProxies(addDependency); | 458 | 337k | if (dstProxyView.proxy()) { | 459 | 37.1k | if (!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment)) { | 460 | 37.1k | this->addSampledTexture(dstProxyView.proxy()); | 461 | 37.1k | } | 462 | 37.1k | if (dstProxyView.dstSampleFlags() & GrDstSampleFlags::kRequiresTextureBarrier) { | 463 | 0 | fRenderPassXferBarriers |= GrXferBarrierFlags::kTexture; | 464 | 0 | } | 465 | 37.1k | addDependency(dstProxyView.proxy(), skgpu::Mipmapped::kNo); | 466 | 37.1k | SkASSERT(!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment) || | 467 | 37.1k | dstProxyView.offset().isZero()); | 468 | 37.1k | } | 469 | | | 470 | 337k | if (processorAnalysis.usesNonCoherentHWBlending()) { | 471 | 0 | fRenderPassXferBarriers |= GrXferBarrierFlags::kBlend; | 472 | 0 | } | 473 | | | 474 | 337k | this->recordOp(std::move(op), usesMSAA, processorAnalysis, clip.doesClip() ? &clip : nullptr, | 475 | 337k | &dstProxyView, caps); | 476 | 337k | } |
Unexecuted instantiation: skgpu::ganesh::OpsTask::addDrawOp(GrDrawingManager*, std::__1::unique_ptr<GrOp, std::__1::default_delete<GrOp> >, bool, GrProcessorSet::Analysis const&, GrAppliedClip&&, GrDstProxyView const&, GrTextureResolveManager, GrCaps const&) |
477 | | |
478 | 1.77k | void OpsTask::endFlush(GrDrawingManager* drawingMgr) { |
479 | 1.77k | fLastClipStackGenID = SK_InvalidUniqueID; |
480 | 1.77k | this->deleteOps(); |
481 | | |
482 | 1.77k | fDeferredProxies.clear(); |
483 | 1.77k | fSampledProxies.clear(); |
484 | 1.77k | fAuditTrail = nullptr; |
485 | | |
486 | 1.77k | GrRenderTask::endFlush(drawingMgr); |
487 | 1.77k | } |
488 | | |
489 | 0 | void OpsTask::onPrePrepare(GrRecordingContext* context) { |
490 | 0 | SkASSERT(this->isClosed()); |
491 | | // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we |
492 | | // can end up with OpsTasks that only have a discard load op and no ops. For vulkan validation |
493 | | // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled |
494 | | // we shouldn't end up with OpsTasks with only discard. |
495 | 0 | if (this->isColorNoOp() || |
496 | 0 | (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { |
497 | 0 | return; |
498 | 0 | } |
499 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
500 | |
|
501 | 0 | GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle); |
502 | 0 | for (const auto& chain : fOpChains) { |
503 | 0 | if (chain.shouldExecute()) { |
504 | 0 | chain.head()->prePrepare(context, |
505 | 0 | dstView, |
506 | 0 | chain.appliedClip(), |
507 | 0 | chain.dstProxyView(), |
508 | 0 | fRenderPassXferBarriers, |
509 | 0 | fColorLoadOp); |
510 | 0 | } |
511 | 0 | } |
512 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::onPrePrepare(GrRecordingContext*) Unexecuted instantiation: skgpu::ganesh::OpsTask::onPrePrepare(GrRecordingContext*) |
513 | | |
514 | 172k | void OpsTask::onPrepare(GrOpFlushState* flushState) { |
515 | 172k | SkASSERT(this->target(0)->peekRenderTarget()); |
516 | 172k | SkASSERT(this->isClosed()); |
517 | | // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we |
518 | | // can end up with OpsTasks that only have a discard load op and no ops. For vulkan validation |
519 | | // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled |
520 | | // we shouldn't end up with OpsTasks with only discard. |
521 | 172k | if (this->isColorNoOp() || |
522 | 172k | (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { |
523 | 244 | return; |
524 | 244 | } |
525 | 172k | TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC); |
526 | | |
527 | 172k | flushState->setSampledProxyArray(&fSampledProxies); |
528 | 172k | GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle); |
529 | | // Loop over the ops that haven't yet been prepared. |
530 | 241k | for (const auto& chain : fOpChains) { |
531 | 241k | if (chain.shouldExecute()) { |
532 | 241k | GrOpFlushState::OpArgs opArgs(chain.head(), |
533 | 241k | dstView, |
534 | 241k | fUsesMSAASurface, |
535 | 241k | chain.appliedClip(), |
536 | 241k | chain.dstProxyView(), |
537 | 241k | fRenderPassXferBarriers, |
538 | 241k | fColorLoadOp); |
539 | | |
540 | 241k | flushState->setOpArgs(&opArgs); |
541 | | |
542 | | // Temporary debugging helper: for debugging prePrepare w/o going through DDLs |
543 | | // Delete once most of the GrOps have an onPrePrepare. |
544 | | // chain.head()->prePrepare(flushState->gpu()->getContext(), &this->target(0), |
545 | | // chain.appliedClip()); |
546 | | |
547 | | // GrOp::prePrepare may or may not have been called at this point |
548 | 241k | chain.head()->prepare(flushState); |
549 | 241k | flushState->setOpArgs(nullptr); |
550 | 241k | } |
551 | 241k | } |
552 | 172k | flushState->setSampledProxyArray(nullptr); |
553 | 172k | } skgpu::ganesh::OpsTask::onPrepare(GrOpFlushState*) Line | Count | Source | 514 | 172k | void OpsTask::onPrepare(GrOpFlushState* flushState) { | 515 | 172k | SkASSERT(this->target(0)->peekRenderTarget()); | 516 | 172k | SkASSERT(this->isClosed()); | 517 | | // TODO: remove the check for discard here once reduced op splitting is turned on. Currently we | 518 | | // can end up with OpsTasks that only have a discard load op and no ops. For vulkan validation | 519 | | // we need to keep that discard and not drop it. Once we have reduce op list splitting enabled | 520 | | // we shouldn't end up with OpsTasks with only discard. | 521 | 172k | if (this->isColorNoOp() || | 522 | 172k | (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) { | 523 | 244 | return; | 524 | 244 | } | 525 | 172k | TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC); | 526 | | | 527 | 172k | flushState->setSampledProxyArray(&fSampledProxies); | 528 | 172k | GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle); | 529 | | // Loop over the ops that haven't yet been prepared. | 530 | 241k | for (const auto& chain : fOpChains) { | 531 | 241k | if (chain.shouldExecute()) { | 532 | 241k | GrOpFlushState::OpArgs opArgs(chain.head(), | 533 | 241k | dstView, | 534 | 241k | fUsesMSAASurface, | 535 | 241k | chain.appliedClip(), | 536 | 241k | chain.dstProxyView(), | 537 | 241k | fRenderPassXferBarriers, | 538 | 241k | fColorLoadOp); | 539 | | | 540 | 241k | flushState->setOpArgs(&opArgs); | 541 | | | 542 | | // Temporary debugging helper: for debugging prePrepare w/o going through DDLs | 543 | | // Delete once most of the GrOps have an onPrePrepare. | 544 | | // chain.head()->prePrepare(flushState->gpu()->getContext(), &this->target(0), | 545 | | // chain.appliedClip()); | 546 | | | 547 | | // GrOp::prePrepare may or may not have been called at this point | 548 | 241k | chain.head()->prepare(flushState); | 549 | 241k | flushState->setOpArgs(nullptr); | 550 | 241k | } | 551 | 241k | } | 552 | 172k | flushState->setSampledProxyArray(nullptr); | 553 | 172k | } |
Unexecuted instantiation: skgpu::ganesh::OpsTask::onPrepare(GrOpFlushState*) |
554 | | |
555 | | // TODO: this is where GrOp::renderTarget is used (which is fine since it |
556 | | // is at flush time). However, we need to store the RenderTargetProxy in the |
557 | | // Ops and instantiate them here. |
558 | 172k | bool OpsTask::onExecute(GrOpFlushState* flushState) { |
559 | 172k | SkASSERT(this->numTargets() == 1); |
560 | 172k | GrRenderTargetProxy* proxy = this->target(0)->asRenderTargetProxy(); |
561 | 172k | SkASSERT(proxy); |
562 | 172k | SK_AT_SCOPE_EXIT(proxy->clearArenas()); |
563 | | |
564 | 172k | if (this->isColorNoOp() || fClippedContentBounds.isEmpty()) { |
565 | 244 | return false; |
566 | 244 | } |
567 | 172k | TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC); |
568 | | |
569 | | // Make sure load ops are not kClear if the GPU needs to use draws for clears |
570 | 172k | SkASSERT(fColorLoadOp != GrLoadOp::kClear || |
571 | 172k | !flushState->gpu()->caps()->performColorClearsAsDraws()); |
572 | | |
573 | 172k | const GrCaps& caps = *flushState->gpu()->caps(); |
574 | 172k | GrRenderTarget* renderTarget = proxy->peekRenderTarget(); |
575 | 172k | SkASSERT(renderTarget); |
576 | | |
577 | 172k | GrAttachment* stencil = nullptr; |
578 | 172k | if (proxy->needsStencil()) { |
579 | 9.87k | SkASSERT(proxy->canUseStencil(caps)); |
580 | 9.87k | if (!flushState->resourceProvider()->attachStencilAttachment(renderTarget, |
581 | 9.87k | fUsesMSAASurface)) { |
582 | 0 | SkDebugf("WARNING: failed to attach a stencil buffer. Rendering will be skipped.\n"); |
583 | 0 | return false; |
584 | 0 | } |
585 | 9.87k | stencil = renderTarget->getStencilAttachment(fUsesMSAASurface); |
586 | 9.87k | } |
587 | | |
588 | 172k | GrLoadOp stencilLoadOp; |
589 | 172k | switch (fInitialStencilContent) { |
590 | 164k | case StencilContent::kDontCare: |
591 | 164k | stencilLoadOp = GrLoadOp::kDiscard; |
592 | 164k | break; |
593 | 644 | case StencilContent::kUserBitsCleared: |
594 | 644 | SkASSERT(!caps.performStencilClearsAsDraws()); |
595 | 644 | SkASSERT(stencil); |
596 | 644 | if (caps.discardStencilValuesAfterRenderPass()) { |
597 | | // Always clear the stencil if it is being discarded after render passes. This is |
598 | | // also an optimization because we are on a tiler and it avoids loading the values |
599 | | // from memory. |
600 | 0 | stencilLoadOp = GrLoadOp::kClear; |
601 | 0 | break; |
602 | 0 | } |
603 | 644 | if (!stencil->hasPerformedInitialClear()) { |
604 | 507 | stencilLoadOp = GrLoadOp::kClear; |
605 | 507 | stencil->markHasPerformedInitialClear(); |
606 | 507 | break; |
607 | 507 | } |
608 | | // SurfaceDrawContexts are required to leave the user stencil bits in a cleared state |
609 | | // once finished, meaning the stencil values will always remain cleared after the |
610 | | // initial clear. Just fall through to reloading the existing (cleared) stencil values |
611 | | // from memory. |
612 | 644 | [[fallthrough]]; |
613 | 7.17k | case StencilContent::kPreserved: |
614 | 7.17k | SkASSERT(stencil); |
615 | 7.17k | stencilLoadOp = GrLoadOp::kLoad; |
616 | 7.17k | break; |
617 | 172k | } |
618 | | |
619 | | // NOTE: If fMustPreserveStencil is set, then we are executing a surfaceDrawContext that split |
620 | | // its opsTask. |
621 | | // |
622 | | // FIXME: We don't currently flag render passes that don't use stencil at all. In that case |
623 | | // their store op might be "discard", and we currently make the assumption that a discard will |
624 | | // not invalidate what's already in main memory. This is probably ok for now, but certainly |
625 | | // something we want to address soon. |
626 | 172k | GrStoreOp stencilStoreOp = (caps.discardStencilValuesAfterRenderPass() && !fMustPreserveStencil) |
627 | 172k | ? GrStoreOp::kDiscard |
628 | 172k | : GrStoreOp::kStore; |
629 | | |
630 | 172k | GrOpsRenderPass* renderPass = create_render_pass(flushState->gpu(), |
631 | 172k | proxy->peekRenderTarget(), |
632 | 172k | fUsesMSAASurface, |
633 | 172k | stencil, |
634 | 172k | fTargetOrigin, |
635 | 172k | fClippedContentBounds, |
636 | 172k | fColorLoadOp, |
637 | 172k | fLoadClearColor, |
638 | 172k | stencilLoadOp, |
639 | 172k | stencilStoreOp, |
640 | 172k | fSampledProxies, |
641 | 172k | fRenderPassXferBarriers); |
642 | | |
643 | 172k | if (!renderPass) { |
644 | 0 | return false; |
645 | 0 | } |
646 | 172k | flushState->setOpsRenderPass(renderPass); |
647 | 172k | renderPass->begin(); |
648 | | |
649 | 172k | GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle); |
650 | | |
651 | | // Draw all the generated geometry. |
652 | 241k | for (const auto& chain : fOpChains) { |
653 | 241k | if (!chain.shouldExecute()) { |
654 | 210 | continue; |
655 | 210 | } |
656 | | |
657 | 241k | GrOpFlushState::OpArgs opArgs(chain.head(), |
658 | 241k | dstView, |
659 | 241k | fUsesMSAASurface, |
660 | 241k | chain.appliedClip(), |
661 | 241k | chain.dstProxyView(), |
662 | 241k | fRenderPassXferBarriers, |
663 | 241k | fColorLoadOp); |
664 | | |
665 | 241k | flushState->setOpArgs(&opArgs); |
666 | 241k | chain.head()->execute(flushState, chain.bounds()); |
667 | 241k | flushState->setOpArgs(nullptr); |
668 | 241k | } |
669 | | |
670 | 172k | renderPass->end(); |
671 | 172k | flushState->gpu()->submit(renderPass); |
672 | 172k | flushState->setOpsRenderPass(nullptr); |
673 | | |
674 | 172k | return true; |
675 | 172k | } skgpu::ganesh::OpsTask::onExecute(GrOpFlushState*) Line | Count | Source | 558 | 172k | bool OpsTask::onExecute(GrOpFlushState* flushState) { | 559 | 172k | SkASSERT(this->numTargets() == 1); | 560 | 172k | GrRenderTargetProxy* proxy = this->target(0)->asRenderTargetProxy(); | 561 | 172k | SkASSERT(proxy); | 562 | 172k | SK_AT_SCOPE_EXIT(proxy->clearArenas()); | 563 | | | 564 | 172k | if (this->isColorNoOp() || fClippedContentBounds.isEmpty()) { | 565 | 244 | return false; | 566 | 244 | } | 567 | 172k | TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC); | 568 | | | 569 | | // Make sure load ops are not kClear if the GPU needs to use draws for clears | 570 | 172k | SkASSERT(fColorLoadOp != GrLoadOp::kClear || | 571 | 172k | !flushState->gpu()->caps()->performColorClearsAsDraws()); | 572 | | | 573 | 172k | const GrCaps& caps = *flushState->gpu()->caps(); | 574 | 172k | GrRenderTarget* renderTarget = proxy->peekRenderTarget(); | 575 | 172k | SkASSERT(renderTarget); | 576 | | | 577 | 172k | GrAttachment* stencil = nullptr; | 578 | 172k | if (proxy->needsStencil()) { | 579 | 9.87k | SkASSERT(proxy->canUseStencil(caps)); | 580 | 9.87k | if (!flushState->resourceProvider()->attachStencilAttachment(renderTarget, | 581 | 9.87k | fUsesMSAASurface)) { | 582 | 0 | SkDebugf("WARNING: failed to attach a stencil buffer. Rendering will be skipped.\n"); | 583 | 0 | return false; | 584 | 0 | } | 585 | 9.87k | stencil = renderTarget->getStencilAttachment(fUsesMSAASurface); | 586 | 9.87k | } | 587 | | | 588 | 172k | GrLoadOp stencilLoadOp; | 589 | 172k | switch (fInitialStencilContent) { | 590 | 164k | case StencilContent::kDontCare: | 591 | 164k | stencilLoadOp = GrLoadOp::kDiscard; | 592 | 164k | break; | 593 | 644 | case StencilContent::kUserBitsCleared: | 594 | 644 | SkASSERT(!caps.performStencilClearsAsDraws()); | 595 | 644 | SkASSERT(stencil); | 596 | 644 | if (caps.discardStencilValuesAfterRenderPass()) { | 597 | | // Always clear the stencil if it is being discarded after render passes. This is | 598 | | // also an optimization because we are on a tiler and it avoids loading the values | 599 | | // from memory. | 600 | 0 | stencilLoadOp = GrLoadOp::kClear; | 601 | 0 | break; | 602 | 0 | } | 603 | 644 | if (!stencil->hasPerformedInitialClear()) { | 604 | 507 | stencilLoadOp = GrLoadOp::kClear; | 605 | 507 | stencil->markHasPerformedInitialClear(); | 606 | 507 | break; | 607 | 507 | } | 608 | | // SurfaceDrawContexts are required to leave the user stencil bits in a cleared state | 609 | | // once finished, meaning the stencil values will always remain cleared after the | 610 | | // initial clear. Just fall through to reloading the existing (cleared) stencil values | 611 | | // from memory. | 612 | 644 | [[fallthrough]]; | 613 | 7.17k | case StencilContent::kPreserved: | 614 | 7.17k | SkASSERT(stencil); | 615 | 7.17k | stencilLoadOp = GrLoadOp::kLoad; | 616 | 7.17k | break; | 617 | 172k | } | 618 | | | 619 | | // NOTE: If fMustPreserveStencil is set, then we are executing a surfaceDrawContext that split | 620 | | // its opsTask. | 621 | | // | 622 | | // FIXME: We don't currently flag render passes that don't use stencil at all. In that case | 623 | | // their store op might be "discard", and we currently make the assumption that a discard will | 624 | | // not invalidate what's already in main memory. This is probably ok for now, but certainly | 625 | | // something we want to address soon. | 626 | 172k | GrStoreOp stencilStoreOp = (caps.discardStencilValuesAfterRenderPass() && !fMustPreserveStencil) | 627 | 172k | ? GrStoreOp::kDiscard | 628 | 172k | : GrStoreOp::kStore; | 629 | | | 630 | 172k | GrOpsRenderPass* renderPass = create_render_pass(flushState->gpu(), | 631 | 172k | proxy->peekRenderTarget(), | 632 | 172k | fUsesMSAASurface, | 633 | 172k | stencil, | 634 | 172k | fTargetOrigin, | 635 | 172k | fClippedContentBounds, | 636 | 172k | fColorLoadOp, | 637 | 172k | fLoadClearColor, | 638 | 172k | stencilLoadOp, | 639 | 172k | stencilStoreOp, | 640 | 172k | fSampledProxies, | 641 | 172k | fRenderPassXferBarriers); | 642 | | | 643 | 172k | if (!renderPass) { | 644 | 0 | return false; | 645 | 0 | } | 646 | 172k | flushState->setOpsRenderPass(renderPass); | 647 | 172k | renderPass->begin(); | 648 | | | 649 | 172k | GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle); | 650 | | | 651 | | // Draw all the generated geometry. | 652 | 241k | for (const auto& chain : fOpChains) { | 653 | 241k | if (!chain.shouldExecute()) { | 654 | 210 | continue; | 655 | 210 | } | 656 | | | 657 | 241k | GrOpFlushState::OpArgs opArgs(chain.head(), | 658 | 241k | dstView, | 659 | 241k | fUsesMSAASurface, | 660 | 241k | chain.appliedClip(), | 661 | 241k | chain.dstProxyView(), | 662 | 241k | fRenderPassXferBarriers, | 663 | 241k | fColorLoadOp); | 664 | | | 665 | 241k | flushState->setOpArgs(&opArgs); | 666 | 241k | chain.head()->execute(flushState, chain.bounds()); | 667 | 241k | flushState->setOpArgs(nullptr); | 668 | 241k | } | 669 | | | 670 | 172k | renderPass->end(); | 671 | 172k | flushState->gpu()->submit(renderPass); | 672 | 172k | flushState->setOpsRenderPass(nullptr); | 673 | | | 674 | 172k | return true; | 675 | 172k | } |
Unexecuted instantiation: skgpu::ganesh::OpsTask::onExecute(GrOpFlushState*) |
676 | | |
677 | 86.2k | void OpsTask::setColorLoadOp(GrLoadOp op, std::array<float, 4> color) { |
678 | 86.2k | fColorLoadOp = op; |
679 | 86.2k | fLoadClearColor = color; |
680 | 86.2k | if (GrLoadOp::kClear == fColorLoadOp) { |
681 | 86.1k | GrSurfaceProxy* proxy = this->target(0); |
682 | 86.1k | SkASSERT(proxy); |
683 | 86.1k | fTotalBounds = proxy->backingStoreBoundsRect(); |
684 | 86.1k | } |
685 | 86.2k | } |
686 | | |
687 | 0 | void OpsTask::reset() { |
688 | 0 | fDeferredProxies.clear(); |
689 | 0 | fSampledProxies.clear(); |
690 | 0 | fClippedContentBounds = SkIRect::MakeEmpty(); |
691 | 0 | fTotalBounds = SkRect::MakeEmpty(); |
692 | 0 | this->deleteOps(); |
693 | 0 | fRenderPassXferBarriers = GrXferBarrierFlags::kNone; |
694 | 0 | } |
695 | | |
696 | 126k | bool OpsTask::canMerge(const OpsTask* opsTask) const { |
697 | 126k | return this->target(0) == opsTask->target(0) && |
698 | 126k | fArenas == opsTask->fArenas && |
699 | 126k | !opsTask->fCannotMergeBackward; |
700 | 126k | } |
701 | | |
702 | 133k | int OpsTask::mergeFrom(SkSpan<const sk_sp<GrRenderTask>> tasks) { |
703 | 133k | int mergedCount = 0; |
704 | 155k | for (const sk_sp<GrRenderTask>& task : tasks) { |
705 | 155k | auto opsTask = task->asOpsTask(); |
706 | 155k | if (!opsTask || !this->canMerge(opsTask)) { |
707 | 130k | break; |
708 | 130k | } |
709 | 24.9k | SkASSERT(fTargetSwizzle == opsTask->fTargetSwizzle); |
710 | 24.9k | SkASSERT(fTargetOrigin == opsTask->fTargetOrigin); |
711 | 24.9k | if (GrLoadOp::kClear == opsTask->fColorLoadOp) { |
712 | | // TODO(11903): Go back to actually dropping ops tasks when we are merged with |
713 | | // color clear. |
714 | 888 | return 0; |
715 | 888 | } |
716 | 24.0k | mergedCount += 1; |
717 | 24.0k | } |
718 | 132k | if (0 == mergedCount) { |
719 | 115k | return 0; |
720 | 115k | } |
721 | | |
722 | 17.1k | SkSpan<const sk_sp<OpsTask>> mergingNodes( |
723 | 17.1k | reinterpret_cast<const sk_sp<OpsTask>*>(tasks.data()), SkToSizeT(mergedCount)); |
724 | 17.1k | int addlDeferredProxyCount = 0; |
725 | 17.1k | int addlProxyCount = 0; |
726 | 17.1k | int addlOpChainCount = 0; |
727 | 22.8k | for (const auto& toMerge : mergingNodes) { |
728 | 22.8k | addlDeferredProxyCount += toMerge->fDeferredProxies.size(); |
729 | 22.8k | addlProxyCount += toMerge->fSampledProxies.size(); |
730 | 22.8k | addlOpChainCount += toMerge->fOpChains.size(); |
731 | 22.8k | fClippedContentBounds.join(toMerge->fClippedContentBounds); |
732 | 22.8k | fTotalBounds.join(toMerge->fTotalBounds); |
733 | 22.8k | fRenderPassXferBarriers |= toMerge->fRenderPassXferBarriers; |
734 | 22.8k | if (fInitialStencilContent == StencilContent::kDontCare) { |
735 | | // Propogate the first stencil content that isn't kDontCare. |
736 | | // |
737 | | // Once the stencil has any kind of initial content that isn't kDontCare, then the |
738 | | // inital contents of subsequent opsTasks that get merged in don't matter. |
739 | | // |
740 | | // (This works because the opsTask all target the same render target and are in |
741 | | // painter's order. kPreserved obviously happens automatically with a merge, and kClear |
742 | | // is also automatic because the contract is for ops to leave the stencil buffer in a |
743 | | // cleared state when finished.) |
744 | 21.0k | fInitialStencilContent = toMerge->fInitialStencilContent; |
745 | 21.0k | } |
746 | 22.8k | fUsesMSAASurface |= toMerge->fUsesMSAASurface; |
747 | 22.8k | SkDEBUGCODE(fNumClips += toMerge->fNumClips); |
748 | 22.8k | } |
749 | | |
750 | 17.1k | fLastClipStackGenID = SK_InvalidUniqueID; |
751 | 17.1k | fDeferredProxies.reserve_exact(fDeferredProxies.size() + addlDeferredProxyCount); |
752 | 17.1k | fSampledProxies.reserve_exact(fSampledProxies.size() + addlProxyCount); |
753 | 17.1k | fOpChains.reserve_exact(fOpChains.size() + addlOpChainCount); |
754 | 22.8k | for (const auto& toMerge : mergingNodes) { |
755 | 22.8k | for (GrRenderTask* renderTask : toMerge->dependents()) { |
756 | 16.9k | renderTask->replaceDependency(toMerge.get(), this); |
757 | 16.9k | } |
758 | 22.8k | for (GrRenderTask* renderTask : toMerge->dependencies()) { |
759 | 8.76k | renderTask->replaceDependent(toMerge.get(), this); |
760 | 8.76k | } |
761 | 22.8k | fDeferredProxies.move_back_n(toMerge->fDeferredProxies.size(), |
762 | 22.8k | toMerge->fDeferredProxies.data()); |
763 | 22.8k | fSampledProxies.move_back_n(toMerge->fSampledProxies.size(), |
764 | 22.8k | toMerge->fSampledProxies.data()); |
765 | 22.8k | fOpChains.move_back_n(toMerge->fOpChains.size(), |
766 | 22.8k | toMerge->fOpChains.data()); |
767 | 22.8k | toMerge->fDeferredProxies.clear(); |
768 | 22.8k | toMerge->fSampledProxies.clear(); |
769 | 22.8k | toMerge->fOpChains.clear(); |
770 | 22.8k | } |
771 | 17.1k | fMustPreserveStencil = mergingNodes.back()->fMustPreserveStencil; |
772 | 17.1k | return mergedCount; |
773 | 132k | } skgpu::ganesh::OpsTask::mergeFrom(SkSpan<sk_sp<GrRenderTask> const>) Line | Count | Source | 702 | 133k | int OpsTask::mergeFrom(SkSpan<const sk_sp<GrRenderTask>> tasks) { | 703 | 133k | int mergedCount = 0; | 704 | 155k | for (const sk_sp<GrRenderTask>& task : tasks) { | 705 | 155k | auto opsTask = task->asOpsTask(); | 706 | 155k | if (!opsTask || !this->canMerge(opsTask)) { | 707 | 130k | break; | 708 | 130k | } | 709 | 24.9k | SkASSERT(fTargetSwizzle == opsTask->fTargetSwizzle); | 710 | 24.9k | SkASSERT(fTargetOrigin == opsTask->fTargetOrigin); | 711 | 24.9k | if (GrLoadOp::kClear == opsTask->fColorLoadOp) { | 712 | | // TODO(11903): Go back to actually dropping ops tasks when we are merged with | 713 | | // color clear. | 714 | 888 | return 0; | 715 | 888 | } | 716 | 24.0k | mergedCount += 1; | 717 | 24.0k | } | 718 | 132k | if (0 == mergedCount) { | 719 | 115k | return 0; | 720 | 115k | } | 721 | | | 722 | 17.1k | SkSpan<const sk_sp<OpsTask>> mergingNodes( | 723 | 17.1k | reinterpret_cast<const sk_sp<OpsTask>*>(tasks.data()), SkToSizeT(mergedCount)); | 724 | 17.1k | int addlDeferredProxyCount = 0; | 725 | 17.1k | int addlProxyCount = 0; | 726 | 17.1k | int addlOpChainCount = 0; | 727 | 22.8k | for (const auto& toMerge : mergingNodes) { | 728 | 22.8k | addlDeferredProxyCount += toMerge->fDeferredProxies.size(); | 729 | 22.8k | addlProxyCount += toMerge->fSampledProxies.size(); | 730 | 22.8k | addlOpChainCount += toMerge->fOpChains.size(); | 731 | 22.8k | fClippedContentBounds.join(toMerge->fClippedContentBounds); | 732 | 22.8k | fTotalBounds.join(toMerge->fTotalBounds); | 733 | 22.8k | fRenderPassXferBarriers |= toMerge->fRenderPassXferBarriers; | 734 | 22.8k | if (fInitialStencilContent == StencilContent::kDontCare) { | 735 | | // Propogate the first stencil content that isn't kDontCare. | 736 | | // | 737 | | // Once the stencil has any kind of initial content that isn't kDontCare, then the | 738 | | // inital contents of subsequent opsTasks that get merged in don't matter. | 739 | | // | 740 | | // (This works because the opsTask all target the same render target and are in | 741 | | // painter's order. kPreserved obviously happens automatically with a merge, and kClear | 742 | | // is also automatic because the contract is for ops to leave the stencil buffer in a | 743 | | // cleared state when finished.) | 744 | 21.0k | fInitialStencilContent = toMerge->fInitialStencilContent; | 745 | 21.0k | } | 746 | 22.8k | fUsesMSAASurface |= toMerge->fUsesMSAASurface; | 747 | 22.8k | SkDEBUGCODE(fNumClips += toMerge->fNumClips); | 748 | 22.8k | } | 749 | | | 750 | 17.1k | fLastClipStackGenID = SK_InvalidUniqueID; | 751 | 17.1k | fDeferredProxies.reserve_exact(fDeferredProxies.size() + addlDeferredProxyCount); | 752 | 17.1k | fSampledProxies.reserve_exact(fSampledProxies.size() + addlProxyCount); | 753 | 17.1k | fOpChains.reserve_exact(fOpChains.size() + addlOpChainCount); | 754 | 22.8k | for (const auto& toMerge : mergingNodes) { | 755 | 22.8k | for (GrRenderTask* renderTask : toMerge->dependents()) { | 756 | 16.9k | renderTask->replaceDependency(toMerge.get(), this); | 757 | 16.9k | } | 758 | 22.8k | for (GrRenderTask* renderTask : toMerge->dependencies()) { | 759 | 8.76k | renderTask->replaceDependent(toMerge.get(), this); | 760 | 8.76k | } | 761 | 22.8k | fDeferredProxies.move_back_n(toMerge->fDeferredProxies.size(), | 762 | 22.8k | toMerge->fDeferredProxies.data()); | 763 | 22.8k | fSampledProxies.move_back_n(toMerge->fSampledProxies.size(), | 764 | 22.8k | toMerge->fSampledProxies.data()); | 765 | 22.8k | fOpChains.move_back_n(toMerge->fOpChains.size(), | 766 | 22.8k | toMerge->fOpChains.data()); | 767 | 22.8k | toMerge->fDeferredProxies.clear(); | 768 | 22.8k | toMerge->fSampledProxies.clear(); | 769 | 22.8k | toMerge->fOpChains.clear(); | 770 | 22.8k | } | 771 | 17.1k | fMustPreserveStencil = mergingNodes.back()->fMustPreserveStencil; | 772 | 17.1k | return mergedCount; | 773 | 132k | } |
Unexecuted instantiation: skgpu::ganesh::OpsTask::mergeFrom(SkSpan<sk_sp<GrRenderTask> const>) |
774 | | |
775 | 86.2k | bool OpsTask::resetForFullscreenClear(CanDiscardPreviousOps canDiscardPreviousOps) { |
776 | 86.2k | if (CanDiscardPreviousOps::kYes == canDiscardPreviousOps || this->isEmpty()) { |
777 | 86.1k | this->deleteOps(); |
778 | 86.1k | fDeferredProxies.clear(); |
779 | 86.1k | fSampledProxies.clear(); |
780 | | |
781 | | // If the opsTask is using a render target which wraps a vulkan command buffer, we can't do |
782 | | // a clear load since we cannot change the render pass that we are using. Thus we fall back |
783 | | // to making a clear op in this case. |
784 | 86.1k | return !this->target(0)->asRenderTargetProxy()->wrapsVkSecondaryCB(); |
785 | 86.1k | } |
786 | | |
787 | | // Could not empty the task, so an op must be added to handle the clear |
788 | 106 | return false; |
789 | 86.2k | } |
790 | | |
791 | 0 | void OpsTask::discard() { |
792 | | // Discard calls to in-progress opsTasks are ignored. Calls at the start update the |
793 | | // opsTasks' color & stencil load ops. |
794 | 0 | if (this->isEmpty()) { |
795 | 0 | fColorLoadOp = GrLoadOp::kDiscard; |
796 | 0 | fInitialStencilContent = StencilContent::kDontCare; |
797 | 0 | fTotalBounds.setEmpty(); |
798 | 0 | } |
799 | 0 | } |
800 | | |
801 | | //////////////////////////////////////////////////////////////////////////////// |
802 | | |
803 | | #if defined(GPU_TEST_UTILS) |
804 | | void OpsTask::dump(const SkString& label, |
805 | | SkString indent, |
806 | | bool printDependencies, |
807 | 0 | bool close) const { |
808 | 0 | GrRenderTask::dump(label, indent, printDependencies, false); |
809 | |
|
810 | 0 | SkDebugf("%sfColorLoadOp: ", indent.c_str()); |
811 | 0 | switch (fColorLoadOp) { |
812 | 0 | case GrLoadOp::kLoad: |
813 | 0 | SkDebugf("kLoad\n"); |
814 | 0 | break; |
815 | 0 | case GrLoadOp::kClear: |
816 | 0 | SkDebugf("kClear {%g, %g, %g, %g}\n", |
817 | 0 | fLoadClearColor[0], |
818 | 0 | fLoadClearColor[1], |
819 | 0 | fLoadClearColor[2], |
820 | 0 | fLoadClearColor[3]); |
821 | 0 | break; |
822 | 0 | case GrLoadOp::kDiscard: |
823 | 0 | SkDebugf("kDiscard\n"); |
824 | 0 | break; |
825 | 0 | } |
826 | | |
827 | 0 | SkDebugf("%sfInitialStencilContent: ", indent.c_str()); |
828 | 0 | switch (fInitialStencilContent) { |
829 | 0 | case StencilContent::kDontCare: |
830 | 0 | SkDebugf("kDontCare\n"); |
831 | 0 | break; |
832 | 0 | case StencilContent::kUserBitsCleared: |
833 | 0 | SkDebugf("kUserBitsCleared\n"); |
834 | 0 | break; |
835 | 0 | case StencilContent::kPreserved: |
836 | 0 | SkDebugf("kPreserved\n"); |
837 | 0 | break; |
838 | 0 | } |
839 | | |
840 | 0 | SkDebugf("%s%d ops:\n", indent.c_str(), fOpChains.size()); |
841 | 0 | for (int i = 0; i < fOpChains.size(); ++i) { |
842 | 0 | SkDebugf("%s*******************************\n", indent.c_str()); |
843 | 0 | if (!fOpChains[i].head()) { |
844 | 0 | SkDebugf("%s%d: <combined forward or failed instantiation>\n", indent.c_str(), i); |
845 | 0 | } else { |
846 | 0 | SkDebugf("%s%d: %s\n", indent.c_str(), i, fOpChains[i].head()->name()); |
847 | 0 | SkRect bounds = fOpChains[i].bounds(); |
848 | 0 | SkDebugf("%sClippedBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", |
849 | 0 | indent.c_str(), |
850 | 0 | bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); |
851 | 0 | for (const auto& op : GrOp::ChainRange<>(fOpChains[i].head())) { |
852 | 0 | SkString info = SkTabString(op.dumpInfo(), 1); |
853 | 0 | SkDebugf("%s%s\n", indent.c_str(), info.c_str()); |
854 | 0 | bounds = op.bounds(); |
855 | 0 | SkDebugf("%s\tClippedBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", |
856 | 0 | indent.c_str(), |
857 | 0 | bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); |
858 | 0 | } |
859 | 0 | } |
860 | 0 | } |
861 | |
|
862 | 0 | if (close) { |
863 | 0 | SkDebugf("%s--------------------------------------------------------------\n\n", |
864 | 0 | indent.c_str()); |
865 | 0 | } |
866 | 0 | } |
867 | | #endif |
868 | | |
869 | | #ifdef SK_DEBUG |
870 | 0 | void OpsTask::visitProxies_debugOnly(const GrVisitProxyFunc& func) const { |
871 | 0 | auto textureFunc = [func](GrSurfaceProxy* tex, skgpu::Mipmapped mipmapped) { |
872 | 0 | func(tex, mipmapped); |
873 | 0 | }; |
874 | |
|
875 | 0 | for (const OpChain& chain : fOpChains) { |
876 | 0 | chain.visitProxies(textureFunc); |
877 | 0 | } |
878 | 0 | } |
879 | | |
880 | | #endif |
881 | | |
882 | | //////////////////////////////////////////////////////////////////////////////// |
883 | | |
884 | 0 | void OpsTask::onMakeSkippable() { |
885 | 0 | this->deleteOps(); |
886 | 0 | fDeferredProxies.clear(); |
887 | 0 | fColorLoadOp = GrLoadOp::kLoad; |
888 | 0 | SkASSERT(this->isColorNoOp()); |
889 | 0 | } Unexecuted instantiation: skgpu::ganesh::OpsTask::onMakeSkippable() Unexecuted instantiation: skgpu::ganesh::OpsTask::onMakeSkippable() |
890 | | |
891 | 2.04M | bool OpsTask::onIsUsed(GrSurfaceProxy* proxyToCheck) const { |
892 | 2.04M | bool used = false; |
893 | 3.08M | for (GrSurfaceProxy* proxy : fSampledProxies) { |
894 | 3.08M | if (proxy == proxyToCheck) { |
895 | 0 | used = true; |
896 | 0 | break; |
897 | 0 | } |
898 | 3.08M | } |
899 | | #ifdef SK_DEBUG |
900 | | bool usedSlow = false; |
901 | 0 | auto visit = [proxyToCheck, &usedSlow](GrSurfaceProxy* p, skgpu::Mipmapped) { |
902 | 0 | if (p == proxyToCheck) { |
903 | 0 | usedSlow = true; |
904 | 0 | } |
905 | 0 | }; |
906 | | this->visitProxies_debugOnly(visit); |
907 | | SkASSERT(used == usedSlow); |
908 | | #endif |
909 | | |
910 | 2.04M | return used; |
911 | 2.04M | } |
912 | | |
913 | 206k | void OpsTask::gatherProxyIntervals(GrResourceAllocator* alloc) const { |
914 | 206k | SkASSERT(this->isClosed()); |
915 | 206k | if (this->isColorNoOp()) { |
916 | 12.9k | return; |
917 | 12.9k | } |
918 | | |
919 | 193k | for (int i = 0; i < fDeferredProxies.size(); ++i) { |
920 | 0 | SkASSERT(!fDeferredProxies[i]->isInstantiated()); |
921 | | // We give all the deferred proxies a write usage at the very start of flushing. This |
922 | | // locks them out of being reused for the entire flush until they are read - and then |
923 | | // they can be recycled. This is a bit unfortunate because a flush can proceed in waves |
924 | | // with sub-flushes. The deferred proxies only need to be pinned from the start of |
925 | | // the sub-flush in which they appear. |
926 | 0 | alloc->addInterval(fDeferredProxies[i], 0, 0, GrResourceAllocator::ActualUse::kNo, |
927 | 0 | GrResourceAllocator::AllowRecycling::kYes); |
928 | 0 | } |
929 | | |
930 | 193k | GrSurfaceProxy* targetSurface = this->target(0); |
931 | 193k | SkASSERT(targetSurface); |
932 | 193k | GrRenderTargetProxy* targetProxy = targetSurface->asRenderTargetProxy(); |
933 | | |
934 | | // Add the interval for all the writes to this OpsTasks's target |
935 | 193k | if (!fOpChains.empty()) { |
936 | 173k | unsigned int cur = alloc->curOp(); |
937 | | |
938 | 173k | alloc->addInterval(targetProxy, cur, cur + fOpChains.size() - 1, |
939 | 173k | GrResourceAllocator::ActualUse::kYes, |
940 | 173k | GrResourceAllocator::AllowRecycling::kYes); |
941 | 173k | } else { |
942 | | // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we |
943 | | // still need to add an interval for the destination so we create a fake op# for |
944 | | // the missing clear op. |
945 | 20.8k | alloc->addInterval(targetProxy, alloc->curOp(), alloc->curOp(), |
946 | 20.8k | GrResourceAllocator::ActualUse::kYes, |
947 | 20.8k | GrResourceAllocator::AllowRecycling::kYes); |
948 | 20.8k | alloc->incOps(); |
949 | 20.8k | } |
950 | | |
951 | 193k | GrResourceAllocator::AllowRecycling allowRecycling = |
952 | 193k | targetProxy->wrapsVkSecondaryCB() ? GrResourceAllocator::AllowRecycling::kNo |
953 | 193k | : GrResourceAllocator::AllowRecycling::kYes; |
954 | | |
955 | 290k | auto gather = [alloc, allowRecycling SkDEBUGCODE(, this)](GrSurfaceProxy* p, skgpu::Mipmapped) { |
956 | 290k | alloc->addInterval(p, |
957 | 290k | alloc->curOp(), |
958 | 290k | alloc->curOp(), |
959 | 290k | GrResourceAllocator::ActualUse::kYes, |
960 | 290k | allowRecycling |
961 | 290k | SkDEBUGCODE(, this->target(0) == p)); |
962 | 290k | }; OpsTask.cpp:skgpu::ganesh::OpsTask::gatherProxyIntervals(GrResourceAllocator*) const::$_0::operator()(GrSurfaceProxy*, skgpu::Mipmapped) const Line | Count | Source | 955 | 290k | auto gather = [alloc, allowRecycling SkDEBUGCODE(, this)](GrSurfaceProxy* p, skgpu::Mipmapped) { | 956 | 290k | alloc->addInterval(p, | 957 | 290k | alloc->curOp(), | 958 | 290k | alloc->curOp(), | 959 | 290k | GrResourceAllocator::ActualUse::kYes, | 960 | 290k | allowRecycling | 961 | 290k | SkDEBUGCODE(, this->target(0) == p)); | 962 | 290k | }; |
Unexecuted instantiation: OpsTask.cpp:skgpu::ganesh::OpsTask::gatherProxyIntervals(GrResourceAllocator*) const::$_3::operator()(GrSurfaceProxy*, skgpu::Mipmapped) const |
963 | | // TODO: visitProxies is expensive. Can we do this with fSampledProxies instead? |
964 | 255k | for (const OpChain& recordedOp : fOpChains) { |
965 | 255k | recordedOp.visitProxies(gather); |
966 | | |
967 | | // Even though the op may have been (re)moved we still need to increment the op count to |
968 | | // keep all the math consistent. |
969 | 255k | alloc->incOps(); |
970 | 255k | } |
971 | 193k | } |
972 | | |
973 | | void OpsTask::recordOp( |
974 | | GrOp::Owner op, bool usesMSAA, GrProcessorSet::Analysis processorAnalysis, |
975 | 342k | GrAppliedClip* clip, const GrDstProxyView* dstProxyView, const GrCaps& caps) { |
976 | 342k | GrSurfaceProxy* proxy = this->target(0); |
977 | | #ifdef SK_DEBUG |
978 | | op->validate(); |
979 | | SkASSERT(processorAnalysis.requiresDstTexture() == (dstProxyView && dstProxyView->proxy())); |
980 | | SkASSERT(proxy); |
981 | | // A closed OpsTask should never receive new/more ops |
982 | | SkASSERT(!this->isClosed()); |
983 | | // Ensure we can support dynamic msaa if the caller is trying to trigger it. |
984 | | if (proxy->asRenderTargetProxy()->numSamples() == 1 && usesMSAA) { |
985 | | SkASSERT(caps.supportsDynamicMSAA(proxy->asRenderTargetProxy())); |
986 | | } |
987 | | #endif |
988 | | |
989 | 342k | if (!op->bounds().isFinite()) { |
990 | 0 | return; |
991 | 0 | } |
992 | | |
993 | 342k | fUsesMSAASurface |= usesMSAA; |
994 | | |
995 | | // Account for this op's bounds before we attempt to combine. |
996 | | // NOTE: The caller should have already called "op->setClippedBounds()" by now, if applicable. |
997 | 342k | fTotalBounds.join(op->bounds()); |
998 | | |
999 | | // Check if there is an op we can combine with by linearly searching back until we either |
1000 | | // 1) check every op |
1001 | | // 2) intersect with something |
1002 | | // 3) find a 'blocker' |
1003 | 342k | GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), proxy->uniqueID()); |
1004 | 342k | GrOP_INFO("opsTask: %d Recording (%s, opID: %u)\n" |
1005 | 342k | "\tBounds [L: %.2f, T: %.2f R: %.2f B: %.2f]\n", |
1006 | 342k | this->uniqueID(), |
1007 | 342k | op->name(), |
1008 | 342k | op->uniqueID(), |
1009 | 342k | op->bounds().fLeft, op->bounds().fTop, |
1010 | 342k | op->bounds().fRight, op->bounds().fBottom); |
1011 | 342k | GrOP_INFO(SkTabString(op->dumpInfo(), 1).c_str()); |
1012 | 342k | GrOP_INFO("\tOutcome:\n"); |
1013 | 342k | int maxCandidates = std::min(kMaxOpChainDistance, fOpChains.size()); |
1014 | 342k | if (maxCandidates) { |
1015 | 179k | int i = 0; |
1016 | 191k | while (true) { |
1017 | 191k | OpChain& candidate = fOpChains.fromBack(i); |
1018 | 191k | op = candidate.appendOp(std::move(op), processorAnalysis, dstProxyView, clip, caps, |
1019 | 191k | fArenas->arenaAlloc(), fAuditTrail); |
1020 | 191k | if (!op) { |
1021 | 100k | return; |
1022 | 100k | } |
1023 | | // Stop going backwards if we would cause a painter's order violation. |
1024 | 90.5k | if (!can_reorder(candidate.bounds(), op->bounds())) { |
1025 | 76.8k | GrOP_INFO("\t\tBackward: Intersects with chain (%s, head opID: %u)\n", |
1026 | 76.8k | candidate.head()->name(), candidate.head()->uniqueID()); |
1027 | 76.8k | break; |
1028 | 76.8k | } |
1029 | 13.6k | if (++i == maxCandidates) { |
1030 | 2.25k | GrOP_INFO("\t\tBackward: Reached max lookback or beginning of op array %d\n", i); |
1031 | 2.25k | break; |
1032 | 2.25k | } |
1033 | 13.6k | } |
1034 | 179k | } else { |
1035 | 162k | GrOP_INFO("\t\tBackward: FirstOp\n"); |
1036 | 162k | } |
1037 | 241k | if (clip) { |
1038 | 167k | clip = fArenas->arenaAlloc()->make<GrAppliedClip>(std::move(*clip)); |
1039 | 167k | SkDEBUGCODE(fNumClips++;) |
1040 | 167k | } |
1041 | 241k | fOpChains.emplace_back(std::move(op), processorAnalysis, clip, dstProxyView); |
1042 | 241k | } |
1043 | | |
1044 | 195k | void OpsTask::forwardCombine(const GrCaps& caps) { |
1045 | 195k | SkASSERT(!this->isClosed()); |
1046 | 195k | GrOP_INFO("opsTask: %d ForwardCombine %d ops:\n", this->uniqueID(), fOpChains.size()); |
1047 | | |
1048 | 274k | for (int i = 0; i < fOpChains.size() - 1; ++i) { |
1049 | 78.9k | OpChain& chain = fOpChains[i]; |
1050 | 78.9k | int maxCandidateIdx = std::min(i + kMaxOpChainDistance, fOpChains.size() - 1); |
1051 | 78.9k | int j = i + 1; |
1052 | 89.0k | while (true) { |
1053 | 89.0k | OpChain& candidate = fOpChains[j]; |
1054 | 89.0k | if (candidate.prependChain(&chain, caps, fArenas->arenaAlloc(), fAuditTrail)) { |
1055 | 210 | break; |
1056 | 210 | } |
1057 | | // Stop traversing if we would cause a painter's order violation. |
1058 | 88.8k | if (!can_reorder(chain.bounds(), candidate.bounds())) { |
1059 | 77.5k | GrOP_INFO( |
1060 | 77.5k | "\t\t%d: chain (%s head opID: %u) -> " |
1061 | 77.5k | "Intersects with chain (%s, head opID: %u)\n", |
1062 | 77.5k | i, chain.head()->name(), chain.head()->uniqueID(), candidate.head()->name(), |
1063 | 77.5k | candidate.head()->uniqueID()); |
1064 | 77.5k | break; |
1065 | 77.5k | } |
1066 | 11.3k | if (++j > maxCandidateIdx) { |
1067 | 1.18k | GrOP_INFO("\t\t%d: chain (%s opID: %u) -> Reached max lookahead or end of array\n", |
1068 | 1.18k | i, chain.head()->name(), chain.head()->uniqueID()); |
1069 | 1.18k | break; |
1070 | 1.18k | } |
1071 | 11.3k | } |
1072 | 78.9k | } |
1073 | 195k | } |
1074 | | |
1075 | | GrRenderTask::ExpectedOutcome OpsTask::onMakeClosed(GrRecordingContext* rContext, |
1076 | 195k | SkIRect* targetUpdateBounds) { |
1077 | 195k | this->forwardCombine(*rContext->priv().caps()); |
1078 | 195k | if (!this->isColorNoOp()) { |
1079 | 182k | GrSurfaceProxy* proxy = this->target(0); |
1080 | | // Use the entire backing store bounds since the GPU doesn't clip automatically to the |
1081 | | // logical dimensions. |
1082 | 182k | SkRect clippedContentBounds = proxy->backingStoreBoundsRect(); |
1083 | | // TODO: If we can fix up GLPrograms test to always intersect the target proxy bounds |
1084 | | // then we can simply assert here that the bounds intersect. |
1085 | 182k | if (clippedContentBounds.intersect(fTotalBounds)) { |
1086 | 182k | clippedContentBounds.roundOut(&fClippedContentBounds); |
1087 | 182k | *targetUpdateBounds = GrNativeRect::MakeIRectRelativeTo( |
1088 | 182k | fTargetOrigin, |
1089 | 182k | this->target(0)->backingStoreDimensions().height(), |
1090 | 182k | fClippedContentBounds); |
1091 | 182k | return ExpectedOutcome::kTargetDirty; |
1092 | 182k | } |
1093 | 182k | } |
1094 | 12.8k | return ExpectedOutcome::kTargetUnchanged; |
1095 | 195k | } |
1096 | | |
1097 | | } // namespace skgpu::ganesh |