• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 "LayerBuilder.h"
18 
19 #include "BakedOpState.h"
20 #include "RenderNode.h"
21 #include "utils/PaintUtils.h"
22 #include "utils/TraceUtils.h"
23 
24 #include <utils/TypeHelpers.h>
25 
26 namespace android {
27 namespace uirenderer {
28 
29 class BatchBase {
30 public:
BatchBase(batchid_t batchId,BakedOpState * op,bool merging)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 
intersects(const Rect & rect) const38     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 
getBatchId() const49     batchid_t getBatchId() const { return mBatchId; }
isMerging() const50     bool isMerging() const { return mMerging; }
51 
getOps() const52     const std::vector<BakedOpState*>& getOps() const { return mOps; }
53 
dump() const54     void dump() const {
55         ALOGD("    Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
56                 this, mBatchId, mMerging, (int) mOps.size(), RECT_ARGS(mBounds));
57     }
58 protected:
59     batchid_t mBatchId;
60     Rect mBounds;
61     std::vector<BakedOpState*> mOps;
62     bool mMerging;
63 };
64 
65 class OpBatch : public BatchBase {
66 public:
OpBatch(batchid_t batchId,BakedOpState * op)67     OpBatch(batchid_t batchId, BakedOpState* op)
68             : BatchBase(batchId, op, false) {
69     }
70 
batchOp(BakedOpState * op)71     void batchOp(BakedOpState* op) {
72         mBounds.unionWith(op->computedState.clippedBounds);
73         mOps.push_back(op);
74     }
75 };
76 
77 class MergingOpBatch : public BatchBase {
78 public:
MergingOpBatch(batchid_t batchId,BakedOpState * op)79     MergingOpBatch(batchid_t batchId, BakedOpState* op)
80             : BatchBase(batchId, op, true)
81             , mClipSideFlags(op->computedState.clipSideFlags) {
82     }
83 
84     /*
85      * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
86      * and clip side flags. Positive bounds delta means new bounds fit in old.
87      */
checkSide(const int currentFlags,const int newFlags,const int side,float boundsDelta)88     static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
89             float boundsDelta) {
90         bool currentClipExists = currentFlags & side;
91         bool newClipExists = newFlags & side;
92 
93         // if current is clipped, we must be able to fit new bounds in current
94         if (boundsDelta > 0 && currentClipExists) return false;
95 
96         // if new is clipped, we must be able to fit current bounds in new
97         if (boundsDelta < 0 && newClipExists) return false;
98 
99         return true;
100     }
101 
paintIsDefault(const SkPaint & paint)102     static bool paintIsDefault(const SkPaint& paint) {
103         return paint.getAlpha() == 255
104                 && paint.getColorFilter() == nullptr
105                 && paint.getShader() == nullptr;
106     }
107 
paintsAreEquivalent(const SkPaint & a,const SkPaint & b)108     static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
109         // Note: don't check color, since all currently mergeable ops can merge across colors
110         return a.getAlpha() == b.getAlpha()
111                 && a.getColorFilter() == b.getColorFilter()
112                 && a.getShader() == b.getShader();
113     }
114 
115     /*
116      * Checks if a (mergeable) op can be merged into this batch
117      *
118      * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
119      * important to consider all paint attributes used in the draw calls in deciding both a) if an
120      * op tries to merge at all, and b) if the op can merge with another set of ops
121      *
122      * False positives can lead to information from the paints of subsequent merged operations being
123      * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
124      */
canMergeWith(BakedOpState * op) const125     bool canMergeWith(BakedOpState* op) const {
126         bool isTextBatch = getBatchId() == OpBatchType::Text
127                 || getBatchId() == OpBatchType::ColorText;
128 
129         // Overlapping other operations is only allowed for text without shadow. For other ops,
130         // multiDraw isn't guaranteed to overdraw correctly
131         if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
132             if (intersects(op->computedState.clippedBounds)) return false;
133         }
134 
135         const BakedOpState* lhs = op;
136         const BakedOpState* rhs = mOps[0];
137 
138         if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
139 
140         // Identical round rect clip state means both ops will clip in the same way, or not at all.
141         // As the state objects are const, we can compare their pointers to determine mergeability
142         if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
143 
144         // Local masks prevent merge, since they're potentially in different coordinate spaces
145         if (lhs->computedState.localProjectionPathMask
146                 || rhs->computedState.localProjectionPathMask) return false;
147 
148         /* Clipping compatibility check
149          *
150          * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
151          * clip for that side.
152          */
153         const int currentFlags = mClipSideFlags;
154         const int newFlags = op->computedState.clipSideFlags;
155         if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
156             const Rect& opBounds = op->computedState.clippedBounds;
157             float boundsDelta = mBounds.left - opBounds.left;
158             if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
159             boundsDelta = mBounds.top - opBounds.top;
160             if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
161 
162             // right and bottom delta calculation reversed to account for direction
163             boundsDelta = opBounds.right - mBounds.right;
164             if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
165             boundsDelta = opBounds.bottom - mBounds.bottom;
166             if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
167         }
168 
169         const SkPaint* newPaint = op->op->paint;
170         const SkPaint* oldPaint = mOps[0]->op->paint;
171 
172         if (newPaint == oldPaint) {
173             // if paints are equal, then modifiers + paint attribs don't need to be compared
174             return true;
175         } else if (newPaint && !oldPaint) {
176             return paintIsDefault(*newPaint);
177         } else if (!newPaint && oldPaint) {
178             return paintIsDefault(*oldPaint);
179         }
180         return paintsAreEquivalent(*newPaint, *oldPaint);
181     }
182 
mergeOp(BakedOpState * op)183     void mergeOp(BakedOpState* op) {
184         mBounds.unionWith(op->computedState.clippedBounds);
185         mOps.push_back(op);
186 
187         // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat
188         // check, and doesn't extend past a side of the clip that's in use by the merged batch.
189         // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect.
190         mClipSideFlags |= op->computedState.clipSideFlags;
191     }
192 
getClipSideFlags() const193     int getClipSideFlags() const { return mClipSideFlags; }
getClipRect() const194     const Rect& getClipRect() const { return mBounds; }
195 
196 private:
197     int mClipSideFlags;
198 };
199 
LayerBuilder(uint32_t width,uint32_t height,const Rect & repaintRect,const BeginLayerOp * beginLayerOp,RenderNode * renderNode)200 LayerBuilder::LayerBuilder(uint32_t width, uint32_t height,
201         const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
202         : width(width)
203         , height(height)
204         , repaintRect(repaintRect)
205         , repaintClip(repaintRect)
206         , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
207         , beginLayerOp(beginLayerOp)
208         , renderNode(renderNode) {}
209 
210 // iterate back toward target to see if anything drawn since should overlap the new op
211 // if no target, merging ops still iterate to find similar batch to insert after
locateInsertIndex(int batchId,const Rect & clippedBounds,BatchBase ** targetBatch,size_t * insertBatchIndex) const212 void LayerBuilder::locateInsertIndex(int batchId, const Rect& clippedBounds,
213         BatchBase** targetBatch, size_t* insertBatchIndex) const {
214     for (int i = mBatches.size() - 1; i >= 0; i--) {
215         BatchBase* overBatch = mBatches[i];
216 
217         if (overBatch == *targetBatch) break;
218 
219         // TODO: also consider shader shared between batch types
220         if (batchId == overBatch->getBatchId()) {
221             *insertBatchIndex = i + 1;
222             if (!*targetBatch) break; // found insert position, quit
223         }
224 
225         if (overBatch->intersects(clippedBounds)) {
226             // NOTE: it may be possible to optimize for special cases where two operations
227             // of the same batch/paint could swap order, such as with a non-mergeable
228             // (clipped) and a mergeable text operation
229             *targetBatch = nullptr;
230             break;
231         }
232     }
233 }
234 
deferLayerClear(const Rect & rect)235 void LayerBuilder::deferLayerClear(const Rect& rect) {
236     mClearRects.push_back(rect);
237 }
238 
onDeferOp(LinearAllocator & allocator,const BakedOpState * bakedState)239 void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
240     if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
241         // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
242         // and issue them together in one draw.
243         flushLayerClears(allocator);
244 
245         if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
246                 && bakedState->computedState.opaqueOverClippedBounds
247                 && bakedState->computedState.clippedBounds.contains(repaintRect)
248                 && !Properties::debugOverdraw)) {
249             // discard all deferred drawing ops, since new one will occlude them
250             clear();
251         }
252     }
253 }
254 
flushLayerClears(LinearAllocator & allocator)255 void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
256     if (CC_UNLIKELY(!mClearRects.empty())) {
257         const int vertCount = mClearRects.size() * 4;
258         // put the verts in the frame allocator, since
259         //     1) SimpleRectsOps needs verts, not rects
260         //     2) even if mClearRects stored verts, std::vectors will move their contents
261         Vertex* const verts = (Vertex*) allocator.create_trivial_array<Vertex>(vertCount);
262 
263         Vertex* currentVert = verts;
264         Rect bounds = mClearRects[0];
265         for (auto&& rect : mClearRects) {
266             bounds.unionWith(rect);
267             Vertex::set(currentVert++, rect.left, rect.top);
268             Vertex::set(currentVert++, rect.right, rect.top);
269             Vertex::set(currentVert++, rect.left, rect.bottom);
270             Vertex::set(currentVert++, rect.right, rect.bottom);
271         }
272         mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
273 
274         // One or more unclipped saveLayers have been enqueued, with deferred clears.
275         // Flush all of these clears with a single draw
276         SkPaint* paint = allocator.create<SkPaint>();
277         paint->setBlendMode(SkBlendMode::kClear);
278         SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds,
279                 Matrix4::identity(), nullptr, paint,
280                 verts, vertCount);
281         BakedOpState* bakedState = BakedOpState::directConstruct(allocator,
282                 &repaintClip, bounds, *op);
283         deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
284     }
285 }
286 
deferUnmergeableOp(LinearAllocator & allocator,BakedOpState * op,batchid_t batchId)287 void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
288         BakedOpState* op, batchid_t batchId) {
289     onDeferOp(allocator, op);
290     OpBatch* targetBatch = mBatchLookup[batchId];
291 
292     size_t insertBatchIndex = mBatches.size();
293     if (targetBatch) {
294         locateInsertIndex(batchId, op->computedState.clippedBounds,
295                 (BatchBase**)(&targetBatch), &insertBatchIndex);
296     }
297 
298     if (targetBatch) {
299         targetBatch->batchOp(op);
300     } else  {
301         // new non-merging batch
302         targetBatch = allocator.create<OpBatch>(batchId, op);
303         mBatchLookup[batchId] = targetBatch;
304         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
305     }
306 }
307 
deferMergeableOp(LinearAllocator & allocator,BakedOpState * op,batchid_t batchId,mergeid_t mergeId)308 void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
309         BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
310     onDeferOp(allocator, op);
311     MergingOpBatch* targetBatch = nullptr;
312 
313     // Try to merge with any existing batch with same mergeId
314     auto getResult = mMergingBatchLookup[batchId].find(mergeId);
315     if (getResult != mMergingBatchLookup[batchId].end()) {
316         targetBatch = getResult->second;
317         if (!targetBatch->canMergeWith(op)) {
318             targetBatch = nullptr;
319         }
320     }
321 
322     size_t insertBatchIndex = mBatches.size();
323     locateInsertIndex(batchId, op->computedState.clippedBounds,
324             (BatchBase**)(&targetBatch), &insertBatchIndex);
325 
326     if (targetBatch) {
327         targetBatch->mergeOp(op);
328     } else  {
329         // new merging batch
330         targetBatch = allocator.create<MergingOpBatch>(batchId, op);
331         mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
332 
333         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
334     }
335 }
336 
replayBakedOpsImpl(void * arg,BakedOpReceiver * unmergedReceivers,MergedOpReceiver * mergedReceivers) const337 void LayerBuilder::replayBakedOpsImpl(void* arg,
338         BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
339     if (renderNode) {
340         ATRACE_FORMAT_BEGIN("Issue HW Layer DisplayList %s %ux%u",
341                 renderNode->getName(), width, height);
342     } else {
343         ATRACE_BEGIN("flush drawing commands");
344     }
345 
346     for (const BatchBase* batch : mBatches) {
347         size_t size = batch->getOps().size();
348         if (size > 1 && batch->isMerging()) {
349             int opId = batch->getOps()[0]->op->opId;
350             const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
351             MergedBakedOpList data = {
352                     batch->getOps().data(),
353                     size,
354                     mergingBatch->getClipSideFlags(),
355                     mergingBatch->getClipRect()
356             };
357             mergedReceivers[opId](arg, data);
358         } else {
359             for (const BakedOpState* op : batch->getOps()) {
360                 unmergedReceivers[op->op->opId](arg, *op);
361             }
362         }
363     }
364     ATRACE_END();
365 }
366 
clear()367 void LayerBuilder::clear() {
368     mBatches.clear();
369     for (int i = 0; i < OpBatchType::Count; i++) {
370         mBatchLookup[i] = nullptr;
371         mMergingBatchLookup[i].clear();
372     }
373 }
374 
dump() const375 void LayerBuilder::dump() const {
376     ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
377             this, width, height, offscreenBuffer, beginLayerOp,
378             renderNode, renderNode ? renderNode->getName() : "-");
379     for (const BatchBase* batch : mBatches) {
380         batch->dump();
381     }
382 }
383 
384 } // namespace uirenderer
385 } // namespace android
386