blob: 9460361f9451e2a8fdc9305f457930e2d86075f7 [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
Chris Craik0b7e8242015-10-28 16:50:44 -070019#include "LayerUpdateQueue.h"
Chris Craik161f54b2015-11-05 11:08:52 -080020#include "RenderNode.h"
Chris Craik98787e62015-11-13 10:55:30 -080021#include "renderstate/OffscreenBufferPool.h"
Chris Craik161f54b2015-11-05 11:08:52 -080022#include "utils/FatVector.h"
23#include "utils/PaintUtils.h"
Chris Craik8ecf41c2015-11-16 10:27:59 -080024#include "utils/TraceUtils.h"
Chris Craikb565df12015-10-05 13:00:52 -070025
Chris Craik161f54b2015-11-05 11:08:52 -080026#include <SkCanvas.h>
Chris Craikd3daa312015-11-06 10:59:56 -080027#include <SkPathOps.h>
Chris Craik161f54b2015-11-05 11:08:52 -080028#include <utils/TypeHelpers.h>
Chris Craikb565df12015-10-05 13:00:52 -070029
30namespace android {
31namespace uirenderer {
32
33class BatchBase {
34
35public:
36 BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
Chris Craik98787e62015-11-13 10:55:30 -080037 : mBatchId(batchId)
38 , mMerging(merging) {
Chris Craikb565df12015-10-05 13:00:52 -070039 mBounds = op->computedState.clippedBounds;
40 mOps.push_back(op);
41 }
42
43 bool intersects(const Rect& rect) const {
44 if (!rect.intersects(mBounds)) return false;
45
46 for (const BakedOpState* op : mOps) {
47 if (rect.intersects(op->computedState.clippedBounds)) {
48 return true;
49 }
50 }
51 return false;
52 }
53
54 batchid_t getBatchId() const { return mBatchId; }
55 bool isMerging() const { return mMerging; }
56
57 const std::vector<BakedOpState*>& getOps() const { return mOps; }
58
59 void dump() const {
Chris Craik6fe991e52015-10-20 09:39:42 -070060 ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
61 this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
Chris Craikb565df12015-10-05 13:00:52 -070062 }
63protected:
64 batchid_t mBatchId;
65 Rect mBounds;
66 std::vector<BakedOpState*> mOps;
67 bool mMerging;
68};
69
70class OpBatch : public BatchBase {
71public:
72 static void* operator new(size_t size, LinearAllocator& allocator) {
73 return allocator.alloc(size);
74 }
75
76 OpBatch(batchid_t batchId, BakedOpState* op)
77 : BatchBase(batchId, op, false) {
78 }
79
80 void batchOp(BakedOpState* op) {
81 mBounds.unionWith(op->computedState.clippedBounds);
82 mOps.push_back(op);
83 }
84};
85
86class MergingOpBatch : public BatchBase {
87public:
88 static void* operator new(size_t size, LinearAllocator& allocator) {
89 return allocator.alloc(size);
90 }
91
92 MergingOpBatch(batchid_t batchId, BakedOpState* op)
93 : BatchBase(batchId, op, true) {
94 }
95
96 /*
97 * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
98 * and clip side flags. Positive bounds delta means new bounds fit in old.
99 */
100 static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
101 float boundsDelta) {
102 bool currentClipExists = currentFlags & side;
103 bool newClipExists = newFlags & side;
104
105 // if current is clipped, we must be able to fit new bounds in current
106 if (boundsDelta > 0 && currentClipExists) return false;
107
108 // if new is clipped, we must be able to fit current bounds in new
109 if (boundsDelta < 0 && newClipExists) return false;
110
111 return true;
112 }
113
114 static bool paintIsDefault(const SkPaint& paint) {
115 return paint.getAlpha() == 255
116 && paint.getColorFilter() == nullptr
117 && paint.getShader() == nullptr;
118 }
119
120 static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
121 return a.getAlpha() == b.getAlpha()
122 && a.getColorFilter() == b.getColorFilter()
123 && a.getShader() == b.getShader();
124 }
125
126 /*
127 * Checks if a (mergeable) op can be merged into this batch
128 *
129 * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
130 * important to consider all paint attributes used in the draw calls in deciding both a) if an
131 * op tries to merge at all, and b) if the op can merge with another set of ops
132 *
133 * False positives can lead to information from the paints of subsequent merged operations being
134 * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
135 */
136 bool canMergeWith(BakedOpState* op) const {
137 bool isTextBatch = getBatchId() == OpBatchType::Text
138 || getBatchId() == OpBatchType::ColorText;
139
140 // Overlapping other operations is only allowed for text without shadow. For other ops,
141 // multiDraw isn't guaranteed to overdraw correctly
142 if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
143 if (intersects(op->computedState.clippedBounds)) return false;
144 }
145
146 const BakedOpState* lhs = op;
147 const BakedOpState* rhs = mOps[0];
148
149 if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
150
151 // Identical round rect clip state means both ops will clip in the same way, or not at all.
152 // As the state objects are const, we can compare their pointers to determine mergeability
153 if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
154 if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
155
156 /* Clipping compatibility check
157 *
158 * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
159 * clip for that side.
160 */
161 const int currentFlags = mClipSideFlags;
162 const int newFlags = op->computedState.clipSideFlags;
163 if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
164 const Rect& opBounds = op->computedState.clippedBounds;
165 float boundsDelta = mBounds.left - opBounds.left;
166 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
167 boundsDelta = mBounds.top - opBounds.top;
168 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
169
170 // right and bottom delta calculation reversed to account for direction
171 boundsDelta = opBounds.right - mBounds.right;
172 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
173 boundsDelta = opBounds.bottom - mBounds.bottom;
174 if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
175 }
176
177 const SkPaint* newPaint = op->op->paint;
178 const SkPaint* oldPaint = mOps[0]->op->paint;
179
180 if (newPaint == oldPaint) {
181 // if paints are equal, then modifiers + paint attribs don't need to be compared
182 return true;
183 } else if (newPaint && !oldPaint) {
184 return paintIsDefault(*newPaint);
185 } else if (!newPaint && oldPaint) {
186 return paintIsDefault(*oldPaint);
187 }
188 return paintsAreEquivalent(*newPaint, *oldPaint);
189 }
190
191 void mergeOp(BakedOpState* op) {
192 mBounds.unionWith(op->computedState.clippedBounds);
193 mOps.push_back(op);
194
195 const int newClipSideFlags = op->computedState.clipSideFlags;
196 mClipSideFlags |= newClipSideFlags;
197
198 const Rect& opClip = op->computedState.clipRect;
199 if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
200 if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
201 if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
202 if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
203 }
204
Chris Craik15c3f192015-12-03 12:16:56 -0800205 bool getClipSideFlags() const { return mClipSideFlags; }
206 const Rect& getClipRect() const { return mClipRect; }
207
Chris Craikb565df12015-10-05 13:00:52 -0700208private:
209 int mClipSideFlags = 0;
210 Rect mClipRect;
211};
212
Chris Craik0b7e8242015-10-28 16:50:44 -0700213OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
Chris Craik98787e62015-11-13 10:55:30 -0800214 const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
Chris Craik0b7e8242015-10-28 16:50:44 -0700215 : width(width)
216 , height(height)
Chris Craik98787e62015-11-13 10:55:30 -0800217 , repaintRect(repaintRect)
Chris Craik0b7e8242015-10-28 16:50:44 -0700218 , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
219 , beginLayerOp(beginLayerOp)
220 , renderNode(renderNode) {}
221
Chris Craik6fe991e52015-10-20 09:39:42 -0700222// iterate back toward target to see if anything drawn since should overlap the new op
Chris Craik818c9fb2015-10-23 14:33:42 -0700223// if no target, merging ops still iterate to find similar batch to insert after
Chris Craik6fe991e52015-10-20 09:39:42 -0700224void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
225 BatchBase** targetBatch, size_t* insertBatchIndex) const {
226 for (int i = mBatches.size() - 1; i >= 0; i--) {
227 BatchBase* overBatch = mBatches[i];
228
229 if (overBatch == *targetBatch) break;
230
231 // TODO: also consider shader shared between batch types
232 if (batchId == overBatch->getBatchId()) {
233 *insertBatchIndex = i + 1;
234 if (!*targetBatch) break; // found insert position, quit
235 }
236
237 if (overBatch->intersects(clippedBounds)) {
238 // NOTE: it may be possible to optimize for special cases where two operations
239 // of the same batch/paint could swap order, such as with a non-mergeable
240 // (clipped) and a mergeable text operation
241 *targetBatch = nullptr;
242 break;
243 }
244 }
245}
246
247void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
248 BakedOpState* op, batchid_t batchId) {
249 OpBatch* targetBatch = mBatchLookup[batchId];
250
251 size_t insertBatchIndex = mBatches.size();
252 if (targetBatch) {
253 locateInsertIndex(batchId, op->computedState.clippedBounds,
254 (BatchBase**)(&targetBatch), &insertBatchIndex);
255 }
256
257 if (targetBatch) {
258 targetBatch->batchOp(op);
259 } else {
260 // new non-merging batch
261 targetBatch = new (allocator) OpBatch(batchId, op);
262 mBatchLookup[batchId] = targetBatch;
263 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
264 }
265}
266
267// insertion point of a new batch, will hopefully be immediately after similar batch
268// (generally, should be similar shader)
269void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
270 BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
271 MergingOpBatch* targetBatch = nullptr;
272
273 // Try to merge with any existing batch with same mergeId
274 auto getResult = mMergingBatchLookup[batchId].find(mergeId);
275 if (getResult != mMergingBatchLookup[batchId].end()) {
276 targetBatch = getResult->second;
277 if (!targetBatch->canMergeWith(op)) {
278 targetBatch = nullptr;
279 }
280 }
281
282 size_t insertBatchIndex = mBatches.size();
283 locateInsertIndex(batchId, op->computedState.clippedBounds,
284 (BatchBase**)(&targetBatch), &insertBatchIndex);
285
286 if (targetBatch) {
287 targetBatch->mergeOp(op);
288 } else {
289 // new merging batch
290 targetBatch = new (allocator) MergingOpBatch(batchId, op);
291 mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
292
293 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
294 }
295}
296
Chris Craik15c3f192015-12-03 12:16:56 -0800297void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg,
298 BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
Chris Craik5854b342015-10-26 15:49:56 -0700299 ATRACE_NAME("flush drawing commands");
Chris Craik6fe991e52015-10-20 09:39:42 -0700300 for (const BatchBase* batch : mBatches) {
Chris Craik15c3f192015-12-03 12:16:56 -0800301 size_t size = batch->getOps().size();
302 if (size > 1 && batch->isMerging()) {
303 int opId = batch->getOps()[0]->op->opId;
304 const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
305 MergedBakedOpList data = {
306 batch->getOps().data(),
307 size,
308 mergingBatch->getClipSideFlags(),
309 mergingBatch->getClipRect()
310 };
311 if (data.clipSideFlags) {
312 // if right or bottom sides aren't used to clip, init them to viewport bounds
313 // in the clip rect, so it can be used to scissor
314 if (!(data.clipSideFlags & OpClipSideFlags::Right)) data.clip.right = width;
315 if (!(data.clipSideFlags & OpClipSideFlags::Bottom)) data.clip.bottom = height;
316 }
317 mergedReceivers[opId](arg, data);
318 } else {
319 for (const BakedOpState* op : batch->getOps()) {
320 unmergedReceivers[op->op->opId](arg, *op);
321 }
Chris Craik6fe991e52015-10-20 09:39:42 -0700322 }
323 }
324}
325
326void OpReorderer::LayerReorderer::dump() const {
Chris Craik0b7e8242015-10-28 16:50:44 -0700327 ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
328 this, width, height, offscreenBuffer, beginLayerOp, renderNode);
Chris Craik6fe991e52015-10-20 09:39:42 -0700329 for (const BatchBase* batch : mBatches) {
330 batch->dump();
331 }
332}
Chris Craikb565df12015-10-05 13:00:52 -0700333
Chris Craik0b7e8242015-10-28 16:50:44 -0700334OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
335 uint32_t viewportWidth, uint32_t viewportHeight,
Chris Craik98787e62015-11-13 10:55:30 -0800336 const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
Chris Craik6fe991e52015-10-20 09:39:42 -0700337 : mCanvasState(*this) {
Chris Craik818c9fb2015-10-23 14:33:42 -0700338 ATRACE_NAME("prepare drawing commands");
Chris Craikb565df12015-10-05 13:00:52 -0700339
Chris Craik98787e62015-11-13 10:55:30 -0800340 mLayerReorderers.reserve(layers.entries().size());
341 mLayerStack.reserve(layers.entries().size());
342
343 // Prepare to defer Fbo0
344 mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
345 mLayerStack.push_back(0);
Chris Craikb565df12015-10-05 13:00:52 -0700346 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
Chris Craikddf22152015-10-14 17:42:47 -0700347 clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Chris Craik98787e62015-11-13 10:55:30 -0800348 lightCenter);
Chris Craik0b7e8242015-10-28 16:50:44 -0700349
350 // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
351 // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
352 for (int i = layers.entries().size() - 1; i >= 0; i--) {
353 RenderNode* layerNode = layers.entries()[i].renderNode;
354 const Rect& layerDamage = layers.entries()[i].damage;
Chris Craik8d1f2122015-11-24 16:40:09 -0800355 layerNode->computeOrdering();
Chris Craik0b7e8242015-10-28 16:50:44 -0700356
Chris Craik8ecf41c2015-11-16 10:27:59 -0800357 // map current light center into RenderNode's coordinate space
358 Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
359 layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter);
360
361 saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
362 layerDamage, lightCenter, nullptr, layerNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700363
364 if (layerNode->getDisplayList()) {
Chris Craik8d1f2122015-11-24 16:40:09 -0800365 deferNodeOps(*layerNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700366 }
367 restoreForLayer();
368 }
369
370 // Defer Fbo0
Chris Craikb565df12015-10-05 13:00:52 -0700371 for (const sp<RenderNode>& node : nodes) {
372 if (node->nothingToDraw()) continue;
Chris Craik8d1f2122015-11-24 16:40:09 -0800373 node->computeOrdering();
Chris Craikb565df12015-10-05 13:00:52 -0700374
Chris Craik0b7e8242015-10-28 16:50:44 -0700375 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
376 deferNodePropsAndOps(*node);
377 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700378 }
379}
380
Chris Craik818c9fb2015-10-23 14:33:42 -0700381void OpReorderer::onViewportInitialized() {}
382
383void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
384
Chris Craik0b7e8242015-10-28 16:50:44 -0700385void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800386 const RenderProperties& properties = node.properties();
387 const Outline& outline = properties.getOutline();
388 if (properties.getAlpha() <= 0
389 || (outline.getShouldClip() && outline.isEmpty())
390 || properties.getScaleX() == 0
391 || properties.getScaleY() == 0) {
392 return; // rejected
393 }
394
395 if (properties.getLeft() != 0 || properties.getTop() != 0) {
396 mCanvasState.translate(properties.getLeft(), properties.getTop());
397 }
398 if (properties.getStaticMatrix()) {
399 mCanvasState.concatMatrix(*properties.getStaticMatrix());
400 } else if (properties.getAnimationMatrix()) {
401 mCanvasState.concatMatrix(*properties.getAnimationMatrix());
402 }
403 if (properties.hasTransformMatrix()) {
404 if (properties.isTransformTranslateOnly()) {
405 mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
406 } else {
407 mCanvasState.concatMatrix(*properties.getTransformMatrix());
408 }
409 }
410
411 const int width = properties.getWidth();
412 const int height = properties.getHeight();
413
414 Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
415 const bool isLayer = properties.effectiveLayerType() != LayerType::None;
416 int clipFlags = properties.getClippingFlags();
417 if (properties.getAlpha() < 1) {
418 if (isLayer) {
419 clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
420 }
421 if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
422 // simply scale rendering content's alpha
423 mCanvasState.scaleAlpha(properties.getAlpha());
424 } else {
425 // schedule saveLayer by initializing saveLayerBounds
426 saveLayerBounds.set(0, 0, width, height);
427 if (clipFlags) {
428 properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
429 clipFlags = 0; // all clipping done by savelayer
430 }
431 }
432
433 if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
434 // pretend alpha always causes savelayer to warn about
435 // performance problem affecting old versions
436 ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
437 }
438 }
439 if (clipFlags) {
440 Rect clipRect;
441 properties.getClippingRectForFlags(clipFlags, &clipRect);
442 mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
443 SkRegion::kIntersect_Op);
444 }
445
446 if (properties.getRevealClip().willClip()) {
447 Rect bounds;
448 properties.getRevealClip().getBounds(&bounds);
449 mCanvasState.setClippingRoundRect(mAllocator,
450 bounds, properties.getRevealClip().getRadius());
451 } else if (properties.getOutline().willClip()) {
452 mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
453 }
454
455 if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
456 // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
Chris Craik0b7e8242015-10-28 16:50:44 -0700457 if (node.getLayer()) {
458 // HW layer
459 LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
460 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
461 if (bakedOpState) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800462 // Node's layer already deferred, schedule it to render into parent layer
Chris Craik0b7e8242015-10-28 16:50:44 -0700463 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
464 }
Chris Craik8ecf41c2015-11-16 10:27:59 -0800465 } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
466 // draw DisplayList contents within temporary, since persisted layer could not be used.
467 // (temp layers are clipped to viewport, since they don't persist offscreen content)
468 SkPaint saveLayerPaint;
469 saveLayerPaint.setAlpha(properties.getAlpha());
470 onBeginLayerOp(*new (mAllocator) BeginLayerOp(
471 saveLayerBounds,
472 Matrix4::identity(),
473 saveLayerBounds,
474 &saveLayerPaint));
Chris Craik8d1f2122015-11-24 16:40:09 -0800475 deferNodeOps(node);
Chris Craik8ecf41c2015-11-16 10:27:59 -0800476 onEndLayerOp(*new (mAllocator) EndLayerOp());
Chris Craik0b7e8242015-10-28 16:50:44 -0700477 } else {
Chris Craik8d1f2122015-11-24 16:40:09 -0800478 deferNodeOps(node);
Chris Craik0b7e8242015-10-28 16:50:44 -0700479 }
480 }
481}
482
Chris Craik161f54b2015-11-05 11:08:52 -0800483typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
484
485template <typename V>
486static void buildZSortedChildList(V* zTranslatedNodes,
487 const DisplayList& displayList, const DisplayList::Chunk& chunk) {
488 if (chunk.beginChildIndex == chunk.endChildIndex) return;
489
490 for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
491 RenderNodeOp* childOp = displayList.getChildren()[i];
492 RenderNode* child = childOp->renderNode;
493 float childZ = child->properties().getZ();
494
495 if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
496 zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
497 childOp->skipInOrderDraw = true;
498 } else if (!child->properties().getProjectBackwards()) {
499 // regular, in order drawing DisplayList
500 childOp->skipInOrderDraw = false;
501 }
502 }
503
504 // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
505 std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
506}
507
508template <typename V>
509static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
510 for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
511 if (zTranslatedNodes[i].key >= 0.0f) return i;
512 }
513 return zTranslatedNodes.size();
514}
515
516template <typename V>
517void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
518 const int size = zTranslatedNodes.size();
519 if (size == 0
520 || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
521 || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
522 // no 3d children to draw
523 return;
524 }
525
526 /**
527 * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
528 * with very similar Z heights to draw together.
529 *
530 * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
531 * underneath both, and neither's shadow is drawn on top of the other.
532 */
533 const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
534 size_t drawIndex, shadowIndex, endIndex;
535 if (mode == ChildrenSelectMode::Negative) {
536 drawIndex = 0;
537 endIndex = nonNegativeIndex;
538 shadowIndex = endIndex; // draw no shadows
539 } else {
540 drawIndex = nonNegativeIndex;
541 endIndex = size;
542 shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
543 }
544
545 float lastCasterZ = 0.0f;
546 while (shadowIndex < endIndex || drawIndex < endIndex) {
547 if (shadowIndex < endIndex) {
548 const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
549 const float casterZ = zTranslatedNodes[shadowIndex].key;
550 // attempt to render the shadow if the caster about to be drawn is its caster,
551 // OR if its caster's Z value is similar to the previous potential caster
552 if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
553 deferShadow(*casterNodeOp);
554
555 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
556 shadowIndex++;
557 continue;
558 }
559 }
560
561 const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
562 deferRenderNodeOp(*childOp);
563 drawIndex++;
564 }
565}
566
567void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
Chris Craikd3daa312015-11-06 10:59:56 -0800568 auto& node = *casterNodeOp.renderNode;
569 auto& properties = node.properties();
570
571 if (properties.getAlpha() <= 0.0f
572 || properties.getOutline().getAlpha() <= 0.0f
573 || !properties.getOutline().getPath()
574 || properties.getScaleX() == 0
575 || properties.getScaleY() == 0) {
576 // no shadow to draw
577 return;
578 }
579
580 const SkPath* casterOutlinePath = properties.getOutline().getPath();
581 const SkPath* revealClipPath = properties.getRevealClip().getPath();
582 if (revealClipPath && revealClipPath->isEmpty()) return;
583
584 float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
585
586 // holds temporary SkPath to store the result of intersections
587 SkPath* frameAllocatedPath = nullptr;
588 const SkPath* casterPath = casterOutlinePath;
589
590 // intersect the shadow-casting path with the reveal, if present
591 if (revealClipPath) {
592 frameAllocatedPath = createFrameAllocatedPath();
593
594 Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
595 casterPath = frameAllocatedPath;
596 }
597
598 // intersect the shadow-casting path with the clipBounds, if present
599 if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
600 if (!frameAllocatedPath) {
601 frameAllocatedPath = createFrameAllocatedPath();
602 }
603 Rect clipBounds;
604 properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
605 SkPath clipBoundsPath;
606 clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
607 clipBounds.right, clipBounds.bottom);
608
609 Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
610 casterPath = frameAllocatedPath;
611 }
612
613 ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
Chris Craik98787e62015-11-13 10:55:30 -0800614 mCanvasState.getLocalClipBounds(),
615 mCanvasState.currentSnapshot()->getRelativeLightCenter());
Chris Craikd3daa312015-11-06 10:59:56 -0800616 BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
617 mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
618 if (CC_LIKELY(bakedOpState)) {
619 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
620 }
Chris Craik161f54b2015-11-05 11:08:52 -0800621}
Chris Craikd3daa312015-11-06 10:59:56 -0800622
Chris Craik8d1f2122015-11-24 16:40:09 -0800623void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
624 const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
625 int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
626
627 // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
628 const DisplayList& displayList = *(renderNode.getDisplayList());
629
630 const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
631 const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
632 const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
633
634 // Transform renderer to match background we're projecting onto
635 // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
636 mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
637
638 // If the projection receiver has an outline, we mask projected content to it
639 // (which we know, apriori, are all tessellated paths)
640 mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
641
642 // draw projected nodes
643 for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
644 RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
645
646 int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
647 mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
648 deferRenderNodeOp(*childOp);
649 mCanvasState.restoreToCount(restoreTo);
650 }
651
652 mCanvasState.restoreToCount(count);
653}
654
Chris Craikb565df12015-10-05 13:00:52 -0700655/**
656 * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
657 *
Chris Craik8d1f2122015-11-24 16:40:09 -0800658 * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
659 * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
Chris Craikb565df12015-10-05 13:00:52 -0700660 */
Chris Craik6fe991e52015-10-20 09:39:42 -0700661#define OP_RECEIVER(Type) \
Chris Craikb565df12015-10-05 13:00:52 -0700662 [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
Chris Craik8d1f2122015-11-24 16:40:09 -0800663void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
Chris Craik15c3f192015-12-03 12:16:56 -0800664 typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op);
665 static OpDispatcher receivers[] = {
Chris Craik6fe991e52015-10-20 09:39:42 -0700666 MAP_OPS(OP_RECEIVER)
Chris Craikb565df12015-10-05 13:00:52 -0700667 };
Chris Craik8d1f2122015-11-24 16:40:09 -0800668
669 // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
670 const DisplayList& displayList = *(renderNode.getDisplayList());
Chris Craikb36af872015-10-16 14:23:12 -0700671 for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
Chris Craik161f54b2015-11-05 11:08:52 -0800672 FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
673 buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
674
675 defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700676 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
Chris Craikb36af872015-10-16 14:23:12 -0700677 const RecordedOp* op = displayList.getOps()[opIndex];
Chris Craikb565df12015-10-05 13:00:52 -0700678 receivers[op->opId](*this, *op);
Chris Craik8d1f2122015-11-24 16:40:09 -0800679
680 if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
681 && displayList.projectionReceiveIndex >= 0
682 && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
683 deferProjectedChildren(renderNode);
684 }
Chris Craikb565df12015-10-05 13:00:52 -0700685 }
Chris Craik161f54b2015-11-05 11:08:52 -0800686 defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700687 }
688}
689
Chris Craik161f54b2015-11-05 11:08:52 -0800690void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
691 if (op.renderNode->nothingToDraw()) return;
Chris Craik6fe991e52015-10-20 09:39:42 -0700692 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craikb565df12015-10-05 13:00:52 -0700693
694 // apply state from RecordedOp
695 mCanvasState.concatMatrix(op.localMatrix);
696 mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
697 op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
698
Chris Craik0b7e8242015-10-28 16:50:44 -0700699 // then apply state from node properties, and defer ops
700 deferNodePropsAndOps(*op.renderNode);
701
Chris Craik6fe991e52015-10-20 09:39:42 -0700702 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700703}
704
Chris Craik161f54b2015-11-05 11:08:52 -0800705void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
706 if (!op.skipInOrderDraw) {
707 deferRenderNodeOp(op);
708 }
709}
710
Chris Craikb565df12015-10-05 13:00:52 -0700711static batchid_t tessellatedBatchId(const SkPaint& paint) {
712 return paint.getPathEffect()
713 ? OpBatchType::AlphaMaskTexture
714 : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
715}
716
717void OpReorderer::onBitmapOp(const BitmapOp& op) {
Chris Craik15c3f192015-12-03 12:16:56 -0800718 BakedOpState* bakedState = tryBakeOpState(op);
719 if (!bakedState) return; // quick rejected
Chris Craikb565df12015-10-05 13:00:52 -0700720
Chris Craik15c3f192015-12-03 12:16:56 -0800721 // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
722 // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
723 // MergingDrawBatch::canMergeWith()
724 if (bakedState->computedState.transform.isSimple()
725 && bakedState->computedState.transform.positiveScale()
726 && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
727 && op.bitmap->colorType() != kAlpha_8_SkColorType) {
728 mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
729 // TODO: AssetAtlas in mergeId
730 currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
731 } else {
732 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
733 }
Chris Craikb565df12015-10-05 13:00:52 -0700734}
735
Chris Craika1717272015-11-19 13:02:43 -0800736void OpReorderer::onLinesOp(const LinesOp& op) {
Chris Craik15c3f192015-12-03 12:16:56 -0800737 BakedOpState* bakedState = tryBakeOpState(op);
738 if (!bakedState) return; // quick rejected
739 currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint));
Chris Craika1717272015-11-19 13:02:43 -0800740}
741
Chris Craikb565df12015-10-05 13:00:52 -0700742void OpReorderer::onRectOp(const RectOp& op) {
Chris Craik15c3f192015-12-03 12:16:56 -0800743 BakedOpState* bakedState = tryBakeOpState(op);
744 if (!bakedState) return; // quick rejected
745 currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint));
Chris Craikb565df12015-10-05 13:00:52 -0700746}
747
748void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
Chris Craik15c3f192015-12-03 12:16:56 -0800749 BakedOpState* bakedState = tryBakeOpState(op);
750 if (!bakedState) return; // quick rejected
751 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
Chris Craikb565df12015-10-05 13:00:52 -0700752}
753
Chris Craika1717272015-11-19 13:02:43 -0800754void OpReorderer::onTextOp(const TextOp& op) {
Chris Craik15c3f192015-12-03 12:16:56 -0800755 BakedOpState* bakedState = tryBakeOpState(op);
756 if (!bakedState) return; // quick rejected
Chris Craika1717272015-11-19 13:02:43 -0800757
758 // TODO: better handling of shader (since we won't care about color then)
759 batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
760 ? OpBatchType::Text : OpBatchType::ColorText;
Chris Craik15c3f192015-12-03 12:16:56 -0800761
762 if (bakedState->computedState.transform.isPureTranslate()
763 && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
764 mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
765 currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
766 } else {
767 currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
768 }
Chris Craika1717272015-11-19 13:02:43 -0800769}
770
Chris Craik8ecf41c2015-11-16 10:27:59 -0800771void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
772 float contentTranslateX, float contentTranslateY,
773 const Rect& repaintRect,
774 const Vector3& lightCenter,
Chris Craik0b7e8242015-10-28 16:50:44 -0700775 const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700776 mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craik818c9fb2015-10-23 14:33:42 -0700777 mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
Chris Craik6fe991e52015-10-20 09:39:42 -0700778 mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
Chris Craik98787e62015-11-13 10:55:30 -0800779 mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
Chris Craik8ecf41c2015-11-16 10:27:59 -0800780 mCanvasState.writableSnapshot()->transform->loadTranslate(
781 contentTranslateX, contentTranslateY, 0);
782 mCanvasState.writableSnapshot()->setClip(
783 repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
Chris Craik98787e62015-11-13 10:55:30 -0800784
Chris Craik8ecf41c2015-11-16 10:27:59 -0800785 // create a new layer repaint, and push its index on the stack
Chris Craik6fe991e52015-10-20 09:39:42 -0700786 mLayerStack.push_back(mLayerReorderers.size());
Chris Craik98787e62015-11-13 10:55:30 -0800787 mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700788}
789
790void OpReorderer::restoreForLayer() {
791 // restore canvas, and pop finished layer off of the stack
792 mCanvasState.restore();
793 mLayerStack.pop_back();
794}
795
796// TODO: test rejection at defer time, where the bounds become empty
797void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800798 uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
799 uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
800
801 auto previous = mCanvasState.currentSnapshot();
802 Vector3 lightCenter = previous->getRelativeLightCenter();
803
804 // Combine all transforms used to present saveLayer content:
805 // parent content transform * canvas transform * bounds offset
806 Matrix4 contentTransform(*previous->transform);
807 contentTransform.multiply(op.localMatrix);
808 contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
809
810 Matrix4 inverseContentTransform;
811 inverseContentTransform.loadInverse(contentTransform);
812
813 // map the light center into layer-relative space
814 inverseContentTransform.mapPoint3d(lightCenter);
815
816 // Clip bounds of temporary layer to parent's clip rect, so:
817 Rect saveLayerBounds(layerWidth, layerHeight);
818 // 1) transform Rect(width, height) into parent's space
819 // note: left/top offsets put in contentTransform above
820 contentTransform.mapRect(saveLayerBounds);
821 // 2) intersect with parent's clip
822 saveLayerBounds.doIntersect(previous->getRenderTargetClip());
823 // 3) and transform back
824 inverseContentTransform.mapRect(saveLayerBounds);
825 saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
826 saveLayerBounds.roundOut();
827
828 // if bounds are reduced, will clip the layer's area by reducing required bounds...
829 layerWidth = saveLayerBounds.getWidth();
830 layerHeight = saveLayerBounds.getHeight();
831 // ...and shifting drawing content to account for left/top side clipping
832 float contentTranslateX = -saveLayerBounds.left;
833 float contentTranslateY = -saveLayerBounds.top;
834
835 saveForLayer(layerWidth, layerHeight,
836 contentTranslateX, contentTranslateY,
837 Rect(layerWidth, layerHeight),
838 lightCenter,
839 &op, nullptr);
Chris Craik6fe991e52015-10-20 09:39:42 -0700840}
Chris Craikb565df12015-10-05 13:00:52 -0700841
Chris Craik6fe991e52015-10-20 09:39:42 -0700842void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700843 const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
Chris Craik6fe991e52015-10-20 09:39:42 -0700844 int finishedLayerIndex = mLayerStack.back();
Chris Craik0b7e8242015-10-28 16:50:44 -0700845
846 restoreForLayer();
Chris Craik6fe991e52015-10-20 09:39:42 -0700847
848 // record the draw operation into the previous layer's list of draw commands
849 // uses state from the associated beginLayerOp, since it has all the state needed for drawing
850 LayerOp* drawLayerOp = new (mAllocator) LayerOp(
851 beginLayerOp.unmappedBounds,
852 beginLayerOp.localMatrix,
853 beginLayerOp.localClipRect,
Chris Craik818c9fb2015-10-23 14:33:42 -0700854 beginLayerOp.paint,
Chris Craik5854b342015-10-26 15:49:56 -0700855 &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
Chris Craik6fe991e52015-10-20 09:39:42 -0700856 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
857
858 if (bakedOpState) {
859 // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
860 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
861 } else {
862 // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
863 mLayerReorderers[finishedLayerIndex].clear();
864 return;
Chris Craikb565df12015-10-05 13:00:52 -0700865 }
866}
867
Chris Craik6fe991e52015-10-20 09:39:42 -0700868void OpReorderer::onLayerOp(const LayerOp& op) {
869 LOG_ALWAYS_FATAL("unsupported");
Chris Craikb565df12015-10-05 13:00:52 -0700870}
871
Chris Craikd3daa312015-11-06 10:59:56 -0800872void OpReorderer::onShadowOp(const ShadowOp& op) {
873 LOG_ALWAYS_FATAL("unsupported");
874}
875
Chris Craikb565df12015-10-05 13:00:52 -0700876} // namespace uirenderer
877} // namespace android