blob: 7c0e2570972a9ad6471cf05e3e6f3eee871680f2 [file] [log] [blame]
Chris Craikb565df12015-10-05 13:00:52 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "OpReorderer.h"
18
19#include "utils/PaintUtils.h"
20#include "RenderNode.h"
21
22#include "SkCanvas.h"
23#include "utils/Trace.h"
24
25namespace android {
26namespace uirenderer {
27
28class BatchBase {
29
30public:
31 BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
32 : mBatchId(batchId)
33 , mMerging(merging) {
34 mBounds = op->computedState.clippedBounds;
35 mOps.push_back(op);
36 }
37
38 bool intersects(const Rect& rect) const {
39 if (!rect.intersects(mBounds)) return false;
40
41 for (const BakedOpState* op : mOps) {
42 if (rect.intersects(op->computedState.clippedBounds)) {
43 return true;
44 }
45 }
46 return false;
47 }
48
49 batchid_t getBatchId() const { return mBatchId; }
50 bool isMerging() const { return mMerging; }
51
52 const std::vector<BakedOpState*>& getOps() const { return mOps; }
53
54 void dump() const {
55 ALOGD(" Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
56 }
57protected:
58 batchid_t mBatchId;
59 Rect mBounds;
60 std::vector<BakedOpState*> mOps;
61 bool mMerging;
62};
63
64class OpBatch : public BatchBase {
65public:
66 static void* operator new(size_t size, LinearAllocator& allocator) {
67 return allocator.alloc(size);
68 }
69
70 OpBatch(batchid_t batchId, BakedOpState* op)
71 : BatchBase(batchId, op, false) {
72 }
73
74 void batchOp(BakedOpState* op) {
75 mBounds.unionWith(op->computedState.clippedBounds);
76 mOps.push_back(op);
77 }
78};
79
80class MergingOpBatch : public BatchBase {
81public:
82 static void* operator new(size_t size, LinearAllocator& allocator) {
83 return allocator.alloc(size);
84 }
85
86 MergingOpBatch(batchid_t batchId, BakedOpState* op)
87 : BatchBase(batchId, op, true) {
88 }
89
90 /*
91 * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
92 * and clip side flags. Positive bounds delta means new bounds fit in old.
93 */
94 static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
95 float boundsDelta) {
96 bool currentClipExists = currentFlags & side;
97 bool newClipExists = newFlags & side;
98
99 // if current is clipped, we must be able to fit new bounds in current
100 if (boundsDelta > 0 && currentClipExists) return false;
101
102 // if new is clipped, we must be able to fit current bounds in new
103 if (boundsDelta < 0 && newClipExists) return false;
104
105 return true;
106 }
107
108 static bool paintIsDefault(const SkPaint& paint) {
109 return paint.getAlpha() == 255
110 && paint.getColorFilter() == nullptr
111 && paint.getShader() == nullptr;
112 }
113
114 static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
115 return a.getAlpha() == b.getAlpha()
116 && a.getColorFilter() == b.getColorFilter()
117 && a.getShader() == b.getShader();
118 }
119
120 /*
121 * Checks if a (mergeable) op can be merged into this batch
122 *
123 * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
124 * important to consider all paint attributes used in the draw calls in deciding both a) if an
125 * op tries to merge at all, and b) if the op can merge with another set of ops
126 *
127 * False positives can lead to information from the paints of subsequent merged operations being
128 * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
129 */
130 bool canMergeWith(BakedOpState* op) const {
131 bool isTextBatch = getBatchId() == OpBatchType::Text
132 || getBatchId() == OpBatchType::ColorText;
133
134 // Overlapping other operations is only allowed for text without shadow. For other ops,
135 // multiDraw isn't guaranteed to overdraw correctly
136 if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
137 if (intersects(op->computedState.clippedBounds)) return false;
138 }
139
140 const BakedOpState* lhs = op;
141 const BakedOpState* rhs = mOps[0];
142
143 if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
144
145 // Identical round rect clip state means both ops will clip in the same way, or not at all.
146 // As the state objects are const, we can compare their pointers to determine mergeability
147 if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
148 if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
149
150 /* Clipping compatibility check
151 *
152 * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
153 * clip for that side.
154 */
155 const int currentFlags = mClipSideFlags;
156 const int newFlags = op->computedState.clipSideFlags;
157 if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
158 const Rect& opBounds = op->computedState.clippedBounds;
159 float boundsDelta = mBounds.left - opBounds.left;
160 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
161 boundsDelta = mBounds.top - opBounds.top;
162 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
163
164 // right and bottom delta calculation reversed to account for direction
165 boundsDelta = opBounds.right - mBounds.right;
166 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
167 boundsDelta = opBounds.bottom - mBounds.bottom;
168 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
169 }
170
171 const SkPaint* newPaint = op->op->paint;
172 const SkPaint* oldPaint = mOps[0]->op->paint;
173
174 if (newPaint == oldPaint) {
175 // if paints are equal, then modifiers + paint attribs don't need to be compared
176 return true;
177 } else if (newPaint && !oldPaint) {
178 return paintIsDefault(*newPaint);
179 } else if (!newPaint && oldPaint) {
180 return paintIsDefault(*oldPaint);
181 }
182 return paintsAreEquivalent(*newPaint, *oldPaint);
183 }
184
185 void mergeOp(BakedOpState* op) {
186 mBounds.unionWith(op->computedState.clippedBounds);
187 mOps.push_back(op);
188
189 const int newClipSideFlags = op->computedState.clipSideFlags;
190 mClipSideFlags |= newClipSideFlags;
191
192 const Rect& opClip = op->computedState.clipRect;
193 if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
194 if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
195 if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
196 if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
197 }
198
199private:
200 int mClipSideFlags = 0;
201 Rect mClipRect;
202};
203
204class NullClient: public CanvasStateClient {
205 void onViewportInitialized() override {}
206 void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
207 GLuint getTargetFbo() const override { return 0; }
208};
209static NullClient sNullClient;
210
211OpReorderer::OpReorderer()
212 : mCanvasState(sNullClient) {
213}
214
Chris Craikddf22152015-10-14 17:42:47 -0700215void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
Chris Craikb565df12015-10-05 13:00:52 -0700216 const std::vector< sp<RenderNode> >& nodes) {
217 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
Chris Craikddf22152015-10-14 17:42:47 -0700218 clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
219 Vector3());
Chris Craikb565df12015-10-05 13:00:52 -0700220 for (const sp<RenderNode>& node : nodes) {
221 if (node->nothingToDraw()) continue;
222
223 // TODO: dedupe this code with onRenderNode()
224 mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
225 if (node->applyViewProperties(mCanvasState)) {
226 // not rejected do ops...
Chris Craik003cc3d2015-10-16 10:24:55 -0700227 const DisplayList& displayList = node->getDisplayList();
Chris Craikb36af872015-10-16 14:23:12 -0700228 deferImpl(displayList);
Chris Craikb565df12015-10-05 13:00:52 -0700229 }
230 mCanvasState.restore();
231 }
232}
233
Chris Craikb36af872015-10-16 14:23:12 -0700234void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) {
Chris Craikb565df12015-10-05 13:00:52 -0700235 ATRACE_NAME("prepare drawing commands");
236 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
237 0, 0, viewportWidth, viewportHeight, Vector3());
Chris Craikb36af872015-10-16 14:23:12 -0700238 deferImpl(displayList);
Chris Craikb565df12015-10-05 13:00:52 -0700239}
240
241/**
242 * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
243 *
244 * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
245 * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
246 */
247#define OP_RECIEVER(Type) \
248 [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
Chris Craikb36af872015-10-16 14:23:12 -0700249void OpReorderer::deferImpl(const DisplayList& displayList) {
Chris Craikb565df12015-10-05 13:00:52 -0700250 static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
251 MAP_OPS(OP_RECIEVER)
252 };
Chris Craikb36af872015-10-16 14:23:12 -0700253 for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
Chris Craikb565df12015-10-05 13:00:52 -0700254 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
Chris Craikb36af872015-10-16 14:23:12 -0700255 const RecordedOp* op = displayList.getOps()[opIndex];
Chris Craikb565df12015-10-05 13:00:52 -0700256 receivers[op->opId](*this, *op);
257 }
258 }
259}
260
261void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
262 ATRACE_NAME("flush drawing commands");
263 for (const BatchBase* batch : mBatches) {
264 // TODO: different behavior based on batch->isMerging()
265 for (const BakedOpState* op : batch->getOps()) {
266 receivers[op->op->opId](arg, *op->op, *op);
267 }
268 }
269}
270
271BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
272 return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
273}
274
275void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
276 if (op.renderNode->nothingToDraw()) {
277 return;
278 }
279 mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
280
281 // apply state from RecordedOp
282 mCanvasState.concatMatrix(op.localMatrix);
283 mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
284 op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
285
286 // apply RenderProperties state
287 if (op.renderNode->applyViewProperties(mCanvasState)) {
288 // not rejected do ops...
Chris Craikb36af872015-10-16 14:23:12 -0700289 deferImpl(op.renderNode->getDisplayList());
Chris Craikb565df12015-10-05 13:00:52 -0700290 }
291 mCanvasState.restore();
292}
293
294static batchid_t tessellatedBatchId(const SkPaint& paint) {
295 return paint.getPathEffect()
296 ? OpBatchType::AlphaMaskTexture
297 : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
298}
299
300void OpReorderer::onBitmapOp(const BitmapOp& op) {
301 BakedOpState* bakedStateOp = bakeOpState(op);
302 if (!bakedStateOp) return; // quick rejected
303
304 mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
305 // TODO: AssetAtlas
306
307 deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
308}
309
310void OpReorderer::onRectOp(const RectOp& op) {
311 BakedOpState* bakedStateOp = bakeOpState(op);
312 if (!bakedStateOp) return; // quick rejected
313 deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
314}
315
316void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
317 BakedOpState* bakedStateOp = bakeOpState(op);
318 if (!bakedStateOp) return; // quick rejected
319 deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
320}
321
322// iterate back toward target to see if anything drawn since should overlap the new op
323// if no target, merging ops still interate to find similar batch to insert after
324void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
325 BatchBase** targetBatch, size_t* insertBatchIndex) const {
Chris Craikddf22152015-10-14 17:42:47 -0700326 for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
Chris Craikb565df12015-10-05 13:00:52 -0700327 BatchBase* overBatch = mBatches[i];
328
329 if (overBatch == *targetBatch) break;
330
331 // TODO: also consider shader shared between batch types
332 if (batchId == overBatch->getBatchId()) {
333 *insertBatchIndex = i + 1;
334 if (!*targetBatch) break; // found insert position, quit
335 }
336
337 if (overBatch->intersects(clippedBounds)) {
338 // NOTE: it may be possible to optimize for special cases where two operations
339 // of the same batch/paint could swap order, such as with a non-mergeable
340 // (clipped) and a mergeable text operation
341 *targetBatch = nullptr;
342 break;
343 }
344 }
345}
346
347void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
348 OpBatch* targetBatch = mBatchLookup[batchId];
349
350 size_t insertBatchIndex = mBatches.size();
351 if (targetBatch) {
352 locateInsertIndex(batchId, op->computedState.clippedBounds,
353 (BatchBase**)(&targetBatch), &insertBatchIndex);
354 }
355
356 if (targetBatch) {
357 targetBatch->batchOp(op);
358 } else {
359 // new non-merging batch
360 targetBatch = new (mAllocator) OpBatch(batchId, op);
361 mBatchLookup[batchId] = targetBatch;
362 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
363 }
364}
365
366// insertion point of a new batch, will hopefully be immediately after similar batch
367// (generally, should be similar shader)
368void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
369 MergingOpBatch* targetBatch = nullptr;
370
371 // Try to merge with any existing batch with same mergeId
372 auto getResult = mMergingBatches[batchId].find(mergeId);
373 if (getResult != mMergingBatches[batchId].end()) {
374 targetBatch = getResult->second;
375 if (!targetBatch->canMergeWith(op)) {
376 targetBatch = nullptr;
377 }
378 }
379
380 size_t insertBatchIndex = mBatches.size();
381 locateInsertIndex(batchId, op->computedState.clippedBounds,
382 (BatchBase**)(&targetBatch), &insertBatchIndex);
383
384 if (targetBatch) {
385 targetBatch->mergeOp(op);
386 } else {
387 // new merging batch
388 targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
389 mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
390
391 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
392 }
393}
394
395void OpReorderer::dump() {
396 for (const BatchBase* batch : mBatches) {
397 batch->dump();
398 }
399}
400
401} // namespace uirenderer
402} // namespace android