1 /*
2 * Copyright 2010 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 "GrRenderTargetOpList.h"
9 #include "GrAuditTrail.h"
10 #include "GrCaps.h"
11 #include "GrGpu.h"
12 #include "GrGpuCommandBuffer.h"
13 #include "GrRect.h"
14 #include "GrRenderTargetContext.h"
15 #include "GrResourceAllocator.h"
16 #include "ops/GrClearOp.h"
17 #include "ops/GrCopySurfaceOp.h"
18 #include "SkTraceEvent.h"
19
20
21 ////////////////////////////////////////////////////////////////////////////////
22
23 // Experimentally we have found that most combining occurs within the first 10 comparisons.
24 static const int kMaxOpLookback = 10;
25 static const int kMaxOpLookahead = 10;
26
GrRenderTargetOpList(GrRenderTargetProxy * proxy,GrResourceProvider * resourceProvider,GrAuditTrail * auditTrail)27 GrRenderTargetOpList::GrRenderTargetOpList(GrRenderTargetProxy* proxy,
28 GrResourceProvider* resourceProvider,
29 GrAuditTrail* auditTrail)
30 : INHERITED(resourceProvider, proxy, auditTrail)
31 , fLastClipStackGenID(SK_InvalidUniqueID)
32 SkDEBUGCODE(, fNumClips(0)) {
33 }
34
~GrRenderTargetOpList()35 GrRenderTargetOpList::~GrRenderTargetOpList() {
36 }
37
38 ////////////////////////////////////////////////////////////////////////////////
39
40 #ifdef SK_DEBUG
dump() const41 void GrRenderTargetOpList::dump() const {
42 INHERITED::dump();
43
44 SkDebugf("ops (%d):\n", fRecordedOps.count());
45 for (int i = 0; i < fRecordedOps.count(); ++i) {
46 SkDebugf("*******************************\n");
47 if (!fRecordedOps[i].fOp) {
48 SkDebugf("%d: <combined forward or failed instantiation>\n", i);
49 } else {
50 SkDebugf("%d: %s\n", i, fRecordedOps[i].fOp->name());
51 SkString str = fRecordedOps[i].fOp->dumpInfo();
52 SkDebugf("%s\n", str.c_str());
53 const SkRect& bounds = fRecordedOps[i].fOp->bounds();
54 SkDebugf("ClippedBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", bounds.fLeft,
55 bounds.fTop, bounds.fRight, bounds.fBottom);
56 }
57 }
58 }
59
visitProxies_debugOnly(const GrOp::VisitProxyFunc & func) const60 void GrRenderTargetOpList::visitProxies_debugOnly(const GrOp::VisitProxyFunc& func) const {
61 for (const RecordedOp& recordedOp : fRecordedOps) {
62 recordedOp.visitProxies(func);
63 }
64 }
65 #endif
66
onPrepare(GrOpFlushState * flushState)67 void GrRenderTargetOpList::onPrepare(GrOpFlushState* flushState) {
68 SkASSERT(fTarget.get()->priv().peekRenderTarget());
69 SkASSERT(this->isClosed());
70 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
71 TRACE_EVENT0("skia", TRACE_FUNC);
72 #endif
73
74 // Loop over the ops that haven't yet been prepared.
75 for (int i = 0; i < fRecordedOps.count(); ++i) {
76 if (fRecordedOps[i].fOp) {
77 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
78 TRACE_EVENT0("skia", fRecordedOps[i].fOp->name());
79 #endif
80 GrOpFlushState::OpArgs opArgs = {
81 fRecordedOps[i].fOp.get(),
82 fTarget.get()->asRenderTargetProxy(),
83 fRecordedOps[i].fAppliedClip,
84 fRecordedOps[i].fDstProxy
85 };
86
87 flushState->setOpArgs(&opArgs);
88 fRecordedOps[i].fOp->prepare(flushState);
89 flushState->setOpArgs(nullptr);
90 }
91 }
92 }
93
create_command_buffer(GrGpu * gpu,GrRenderTarget * rt,GrSurfaceOrigin origin,GrLoadOp colorLoadOp,GrColor loadClearColor,GrLoadOp stencilLoadOp)94 static std::unique_ptr<GrGpuRTCommandBuffer> create_command_buffer(GrGpu* gpu,
95 GrRenderTarget* rt,
96 GrSurfaceOrigin origin,
97 GrLoadOp colorLoadOp,
98 GrColor loadClearColor,
99 GrLoadOp stencilLoadOp) {
100 const GrGpuRTCommandBuffer::LoadAndStoreInfo kColorLoadStoreInfo {
101 colorLoadOp,
102 GrStoreOp::kStore,
103 loadClearColor
104 };
105
106 // TODO:
107 // We would like to (at this level) only ever clear & discard. We would need
108 // to stop splitting up higher level opLists for copyOps to achieve that.
109 // Note: we would still need SB loads and stores but they would happen at a
110 // lower level (inside the VK command buffer).
111 const GrGpuRTCommandBuffer::StencilLoadAndStoreInfo stencilLoadAndStoreInfo {
112 stencilLoadOp,
113 GrStoreOp::kStore,
114 };
115
116 std::unique_ptr<GrGpuRTCommandBuffer> buffer(
117 gpu->createCommandBuffer(rt, origin,
118 kColorLoadStoreInfo,
119 stencilLoadAndStoreInfo));
120 return buffer;
121 }
122
finish_command_buffer(GrGpuRTCommandBuffer * buffer)123 static inline void finish_command_buffer(GrGpuRTCommandBuffer* buffer) {
124 if (!buffer) {
125 return;
126 }
127
128 buffer->end();
129 buffer->submit();
130 }
131
132 // TODO: this is where GrOp::renderTarget is used (which is fine since it
133 // is at flush time). However, we need to store the RenderTargetProxy in the
134 // Ops and instantiate them here.
onExecute(GrOpFlushState * flushState)135 bool GrRenderTargetOpList::onExecute(GrOpFlushState* flushState) {
136 if (0 == fRecordedOps.count() && GrLoadOp::kClear != fColorLoadOp) {
137 return false;
138 }
139
140 SkASSERT(fTarget.get()->priv().peekRenderTarget());
141 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
142 TRACE_EVENT0("skia", TRACE_FUNC);
143 #endif
144
145 // TODO: at the very least, we want the stencil store op to always be discard (at this
146 // level). In Vulkan, sub-command buffers would still need to load & store the stencil buffer.
147 std::unique_ptr<GrGpuRTCommandBuffer> commandBuffer = create_command_buffer(
148 flushState->gpu(),
149 fTarget.get()->priv().peekRenderTarget(),
150 fTarget.get()->origin(),
151 fColorLoadOp, fLoadClearColor,
152 fStencilLoadOp);
153 flushState->setCommandBuffer(commandBuffer.get());
154 commandBuffer->begin();
155
156 // Draw all the generated geometry.
157 for (int i = 0; i < fRecordedOps.count(); ++i) {
158 if (!fRecordedOps[i].fOp) {
159 continue;
160 }
161 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
162 TRACE_EVENT0("skia", fRecordedOps[i].fOp->name());
163 #endif
164
165 GrOpFlushState::OpArgs opArgs {
166 fRecordedOps[i].fOp.get(),
167 fTarget.get()->asRenderTargetProxy(),
168 fRecordedOps[i].fAppliedClip,
169 fRecordedOps[i].fDstProxy
170 };
171
172 flushState->setOpArgs(&opArgs);
173 fRecordedOps[i].fOp->execute(flushState);
174 flushState->setOpArgs(nullptr);
175 }
176
177 finish_command_buffer(commandBuffer.get());
178 flushState->setCommandBuffer(nullptr);
179
180 return true;
181 }
182
endFlush()183 void GrRenderTargetOpList::endFlush() {
184 fLastClipStackGenID = SK_InvalidUniqueID;
185 fRecordedOps.reset();
186 fClipAllocator.reset();
187 INHERITED::endFlush();
188 }
189
discard()190 void GrRenderTargetOpList::discard() {
191 // Discard calls to in-progress opLists are ignored. Calls at the start update the
192 // opLists' color & stencil load ops.
193 if (this->isEmpty()) {
194 fColorLoadOp = GrLoadOp::kDiscard;
195 fStencilLoadOp = GrLoadOp::kDiscard;
196 }
197 }
198
fullClear(const GrCaps & caps,GrColor color)199 void GrRenderTargetOpList::fullClear(const GrCaps& caps, GrColor color) {
200
201 // This is conservative. If the opList is marked as needing a stencil buffer then there
202 // may be a prior op that writes to the stencil buffer. Although the clear will ignore the
203 // stencil buffer, following draw ops may not so we can't get rid of all the preceding ops.
204 // Beware! If we ever add any ops that have a side effect beyond modifying the stencil
205 // buffer we will need a more elaborate tracking system (skbug.com/7002).
206 if (this->isEmpty() || !fTarget.get()->asRenderTargetProxy()->needsStencil()) {
207 fRecordedOps.reset();
208 fDeferredProxies.reset();
209 fColorLoadOp = GrLoadOp::kClear;
210 fLoadClearColor = color;
211 return;
212 }
213
214 std::unique_ptr<GrClearOp> op(GrClearOp::Make(GrFixedClip::Disabled(), color, fTarget.get()));
215 if (!op) {
216 return;
217 }
218
219 this->recordOp(std::move(op), caps);
220 }
221
222 ////////////////////////////////////////////////////////////////////////////////
223
224 // This closely parallels GrTextureOpList::copySurface but renderTargetOpLists
225 // also store the applied clip and dest proxy with the op
copySurface(const GrCaps & caps,GrSurfaceProxy * dst,GrSurfaceProxy * src,const SkIRect & srcRect,const SkIPoint & dstPoint)226 bool GrRenderTargetOpList::copySurface(const GrCaps& caps,
227 GrSurfaceProxy* dst,
228 GrSurfaceProxy* src,
229 const SkIRect& srcRect,
230 const SkIPoint& dstPoint) {
231 SkASSERT(dst->asRenderTargetProxy() == fTarget.get());
232 std::unique_ptr<GrOp> op = GrCopySurfaceOp::Make(dst, src, srcRect, dstPoint);
233 if (!op) {
234 return false;
235 }
236
237 this->addOp(std::move(op), caps);
238 return true;
239 }
240
purgeOpsWithUninstantiatedProxies()241 void GrRenderTargetOpList::purgeOpsWithUninstantiatedProxies() {
242 bool hasUninstantiatedProxy = false;
243 auto checkInstantiation = [ &hasUninstantiatedProxy ] (GrSurfaceProxy* p) {
244 if (!p->priv().isInstantiated()) {
245 hasUninstantiatedProxy = true;
246 }
247 };
248 for (RecordedOp& recordedOp : fRecordedOps) {
249 hasUninstantiatedProxy = false;
250 recordedOp.visitProxies(checkInstantiation);
251 if (hasUninstantiatedProxy) {
252 // When instantiation of the proxy fails we drop the Op
253 recordedOp.fOp = nullptr;
254 }
255 }
256 }
257
gatherProxyIntervals(GrResourceAllocator * alloc) const258 void GrRenderTargetOpList::gatherProxyIntervals(GrResourceAllocator* alloc) const {
259 unsigned int cur = alloc->numOps();
260
261 for (int i = 0; i < fDeferredProxies.count(); ++i) {
262 SkASSERT(!fDeferredProxies[i]->priv().isInstantiated());
263 // We give all the deferred proxies a write usage at the very start of flushing. This
264 // locks them out of being reused for the entire flush until they are read - and then
265 // they can be recycled. This is a bit unfortunate because a flush can proceed in waves
266 // with sub-flushes. The deferred proxies only need to be pinned from the start of
267 // the sub-flush in which they appear.
268 alloc->addInterval(fDeferredProxies[i], 0, 0);
269 }
270
271 // Add the interval for all the writes to this opList's target
272 if (fRecordedOps.count()) {
273 alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
274 } else {
275 // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we
276 // still need to add an interval for the destination so we create a fake op# for
277 // the missing clear op.
278 alloc->addInterval(fTarget.get());
279 alloc->incOps();
280 }
281
282 auto gather = [ alloc SkDEBUGCODE(, this) ] (GrSurfaceProxy* p) {
283 alloc->addInterval(p SkDEBUGCODE(, fTarget.get() == p));
284 };
285 for (const RecordedOp& recordedOp : fRecordedOps) {
286 recordedOp.visitProxies(gather); // only diff from the GrTextureOpList version
287
288 // Even though the op may have been moved we still need to increment the op count to
289 // keep all the math consistent.
290 alloc->incOps();
291 }
292 }
293
can_reorder(const SkRect & a,const SkRect & b)294 static inline bool can_reorder(const SkRect& a, const SkRect& b) { return !GrRectsOverlap(a, b); }
295
combineIfPossible(const RecordedOp & a,GrOp * b,const GrAppliedClip * bClip,const DstProxy * bDstProxy,const GrCaps & caps)296 bool GrRenderTargetOpList::combineIfPossible(const RecordedOp& a, GrOp* b,
297 const GrAppliedClip* bClip,
298 const DstProxy* bDstProxy,
299 const GrCaps& caps) {
300 if (a.fAppliedClip) {
301 if (!bClip) {
302 return false;
303 }
304 if (*a.fAppliedClip != *bClip) {
305 return false;
306 }
307 } else if (bClip) {
308 return false;
309 }
310 if (bDstProxy) {
311 if (a.fDstProxy != *bDstProxy) {
312 return false;
313 }
314 } else if (a.fDstProxy.proxy()) {
315 return false;
316 }
317 return a.fOp->combineIfPossible(b, caps);
318 }
319
recordOp(std::unique_ptr<GrOp> op,const GrCaps & caps,GrAppliedClip * clip,const DstProxy * dstProxy)320 void GrRenderTargetOpList::recordOp(std::unique_ptr<GrOp> op,
321 const GrCaps& caps,
322 GrAppliedClip* clip,
323 const DstProxy* dstProxy) {
324 SkASSERT(fTarget.get());
325
326 // A closed GrOpList should never receive new/more ops
327 SkASSERT(!this->isClosed());
328
329 // Check if there is an op we can combine with by linearly searching back until we either
330 // 1) check every op
331 // 2) intersect with something
332 // 3) find a 'blocker'
333 GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), fTarget.get()->uniqueID());
334 GrOP_INFO("opList: %d Recording (%s, opID: %u)\n"
335 "\tBounds [L: %.2f, T: %.2f R: %.2f B: %.2f]\n",
336 this->uniqueID(),
337 op->name(),
338 op->uniqueID(),
339 op->bounds().fLeft, op->bounds().fTop,
340 op->bounds().fRight, op->bounds().fBottom);
341 GrOP_INFO(SkTabString(op->dumpInfo(), 1).c_str());
342 GrOP_INFO("\tOutcome:\n");
343 int maxCandidates = SkTMin(kMaxOpLookback, fRecordedOps.count());
344 // If we don't have a valid destination render target then we cannot reorder.
345 if (maxCandidates) {
346 int i = 0;
347 while (true) {
348 const RecordedOp& candidate = fRecordedOps.fromBack(i);
349
350 if (this->combineIfPossible(candidate, op.get(), clip, dstProxy, caps)) {
351 GrOP_INFO("\t\tBackward: Combining with (%s, opID: %u)\n", candidate.fOp->name(),
352 candidate.fOp->uniqueID());
353 GrOP_INFO("\t\t\tBackward: Combined op info:\n");
354 GrOP_INFO(SkTabString(candidate.fOp->dumpInfo(), 4).c_str());
355 GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, candidate.fOp.get(), op.get());
356 return;
357 }
358 // Stop going backwards if we would cause a painter's order violation.
359 if (!can_reorder(fRecordedOps.fromBack(i).fOp->bounds(), op->bounds())) {
360 GrOP_INFO("\t\tBackward: Intersects with (%s, opID: %u)\n", candidate.fOp->name(),
361 candidate.fOp->uniqueID());
362 break;
363 }
364 ++i;
365 if (i == maxCandidates) {
366 GrOP_INFO("\t\tBackward: Reached max lookback or beginning of op array %d\n", i);
367 break;
368 }
369 }
370 } else {
371 GrOP_INFO("\t\tBackward: FirstOp\n");
372 }
373 GR_AUDIT_TRAIL_OP_RESULT_NEW(fAuditTrail, op);
374 if (clip) {
375 clip = fClipAllocator.make<GrAppliedClip>(std::move(*clip));
376 SkDEBUGCODE(fNumClips++;)
377 }
378 fRecordedOps.emplace_back(std::move(op), clip, dstProxy);
379 fRecordedOps.back().fOp->wasRecorded(this);
380 }
381
forwardCombine(const GrCaps & caps)382 void GrRenderTargetOpList::forwardCombine(const GrCaps& caps) {
383 SkASSERT(!this->isClosed());
384
385 GrOP_INFO("opList: %d ForwardCombine %d ops:\n", this->uniqueID(), fRecordedOps.count());
386
387 for (int i = 0; i < fRecordedOps.count() - 1; ++i) {
388 GrOp* op = fRecordedOps[i].fOp.get();
389
390 int maxCandidateIdx = SkTMin(i + kMaxOpLookahead, fRecordedOps.count() - 1);
391 int j = i + 1;
392 while (true) {
393 const RecordedOp& candidate = fRecordedOps[j];
394
395 if (this->combineIfPossible(fRecordedOps[i], candidate.fOp.get(),
396 candidate.fAppliedClip, &candidate.fDstProxy, caps)) {
397 GrOP_INFO("\t\t%d: (%s opID: %u) -> Combining with (%s, opID: %u)\n",
398 i, op->name(), op->uniqueID(),
399 candidate.fOp->name(), candidate.fOp->uniqueID());
400 GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, op, candidate.fOp.get());
401 fRecordedOps[j].fOp = std::move(fRecordedOps[i].fOp);
402 break;
403 }
404 // Stop traversing if we would cause a painter's order violation.
405 if (!can_reorder(fRecordedOps[j].fOp->bounds(), op->bounds())) {
406 GrOP_INFO("\t\t%d: (%s opID: %u) -> Intersects with (%s, opID: %u)\n",
407 i, op->name(), op->uniqueID(),
408 candidate.fOp->name(), candidate.fOp->uniqueID());
409 break;
410 }
411 ++j;
412 if (j > maxCandidateIdx) {
413 GrOP_INFO("\t\t%d: (%s opID: %u) -> Reached max lookahead or end of array\n",
414 i, op->name(), op->uniqueID());
415 break;
416 }
417 }
418 }
419 }
420
421