blob: 9cbd9c2d9ffcf088ed35ffc5e546e2b3b506cfe3 [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
205private:
206 int mClipSideFlags = 0;
207 Rect mClipRect;
208};
209
Chris Craik0b7e8242015-10-28 16:50:44 -0700210OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
Chris Craik98787e62015-11-13 10:55:30 -0800211 const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
Chris Craik0b7e8242015-10-28 16:50:44 -0700212 : width(width)
213 , height(height)
Chris Craik98787e62015-11-13 10:55:30 -0800214 , repaintRect(repaintRect)
Chris Craik0b7e8242015-10-28 16:50:44 -0700215 , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
216 , beginLayerOp(beginLayerOp)
217 , renderNode(renderNode) {}
218
Chris Craik6fe991e52015-10-20 09:39:42 -0700219// iterate back toward target to see if anything drawn since should overlap the new op
Chris Craik818c9fb2015-10-23 14:33:42 -0700220// if no target, merging ops still iterate to find similar batch to insert after
Chris Craik6fe991e52015-10-20 09:39:42 -0700221void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
222 BatchBase** targetBatch, size_t* insertBatchIndex) const {
223 for (int i = mBatches.size() - 1; i >= 0; i--) {
224 BatchBase* overBatch = mBatches[i];
225
226 if (overBatch == *targetBatch) break;
227
228 // TODO: also consider shader shared between batch types
229 if (batchId == overBatch->getBatchId()) {
230 *insertBatchIndex = i + 1;
231 if (!*targetBatch) break; // found insert position, quit
232 }
233
234 if (overBatch->intersects(clippedBounds)) {
235 // NOTE: it may be possible to optimize for special cases where two operations
236 // of the same batch/paint could swap order, such as with a non-mergeable
237 // (clipped) and a mergeable text operation
238 *targetBatch = nullptr;
239 break;
240 }
241 }
242}
243
244void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
245 BakedOpState* op, batchid_t batchId) {
246 OpBatch* targetBatch = mBatchLookup[batchId];
247
248 size_t insertBatchIndex = mBatches.size();
249 if (targetBatch) {
250 locateInsertIndex(batchId, op->computedState.clippedBounds,
251 (BatchBase**)(&targetBatch), &insertBatchIndex);
252 }
253
254 if (targetBatch) {
255 targetBatch->batchOp(op);
256 } else {
257 // new non-merging batch
258 targetBatch = new (allocator) OpBatch(batchId, op);
259 mBatchLookup[batchId] = targetBatch;
260 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
261 }
262}
263
264// insertion point of a new batch, will hopefully be immediately after similar batch
265// (generally, should be similar shader)
266void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
267 BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
268 MergingOpBatch* targetBatch = nullptr;
269
270 // Try to merge with any existing batch with same mergeId
271 auto getResult = mMergingBatchLookup[batchId].find(mergeId);
272 if (getResult != mMergingBatchLookup[batchId].end()) {
273 targetBatch = getResult->second;
274 if (!targetBatch->canMergeWith(op)) {
275 targetBatch = nullptr;
276 }
277 }
278
279 size_t insertBatchIndex = mBatches.size();
280 locateInsertIndex(batchId, op->computedState.clippedBounds,
281 (BatchBase**)(&targetBatch), &insertBatchIndex);
282
283 if (targetBatch) {
284 targetBatch->mergeOp(op);
285 } else {
286 // new merging batch
287 targetBatch = new (allocator) MergingOpBatch(batchId, op);
288 mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
289
290 mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
291 }
292}
293
Chris Craik5854b342015-10-26 15:49:56 -0700294void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const {
295 ATRACE_NAME("flush drawing commands");
Chris Craik6fe991e52015-10-20 09:39:42 -0700296 for (const BatchBase* batch : mBatches) {
297 // TODO: different behavior based on batch->isMerging()
298 for (const BakedOpState* op : batch->getOps()) {
299 receivers[op->op->opId](arg, *op->op, *op);
300 }
301 }
302}
303
304void OpReorderer::LayerReorderer::dump() const {
Chris Craik0b7e8242015-10-28 16:50:44 -0700305 ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
306 this, width, height, offscreenBuffer, beginLayerOp, renderNode);
Chris Craik6fe991e52015-10-20 09:39:42 -0700307 for (const BatchBase* batch : mBatches) {
308 batch->dump();
309 }
310}
Chris Craikb565df12015-10-05 13:00:52 -0700311
Chris Craik0b7e8242015-10-28 16:50:44 -0700312OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
313 uint32_t viewportWidth, uint32_t viewportHeight,
Chris Craik98787e62015-11-13 10:55:30 -0800314 const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
Chris Craik6fe991e52015-10-20 09:39:42 -0700315 : mCanvasState(*this) {
Chris Craik818c9fb2015-10-23 14:33:42 -0700316 ATRACE_NAME("prepare drawing commands");
Chris Craikb565df12015-10-05 13:00:52 -0700317
Chris Craik98787e62015-11-13 10:55:30 -0800318 mLayerReorderers.reserve(layers.entries().size());
319 mLayerStack.reserve(layers.entries().size());
320
321 // Prepare to defer Fbo0
322 mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
323 mLayerStack.push_back(0);
Chris Craikb565df12015-10-05 13:00:52 -0700324 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
Chris Craikddf22152015-10-14 17:42:47 -0700325 clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Chris Craik98787e62015-11-13 10:55:30 -0800326 lightCenter);
Chris Craik0b7e8242015-10-28 16:50:44 -0700327
328 // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
329 // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
330 for (int i = layers.entries().size() - 1; i >= 0; i--) {
331 RenderNode* layerNode = layers.entries()[i].renderNode;
332 const Rect& layerDamage = layers.entries()[i].damage;
Chris Craik8d1f2122015-11-24 16:40:09 -0800333 layerNode->computeOrdering();
Chris Craik0b7e8242015-10-28 16:50:44 -0700334
Chris Craik8ecf41c2015-11-16 10:27:59 -0800335 // map current light center into RenderNode's coordinate space
336 Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
337 layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter);
338
339 saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
340 layerDamage, lightCenter, nullptr, layerNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700341
342 if (layerNode->getDisplayList()) {
Chris Craik8d1f2122015-11-24 16:40:09 -0800343 deferNodeOps(*layerNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700344 }
345 restoreForLayer();
346 }
347
348 // Defer Fbo0
Chris Craikb565df12015-10-05 13:00:52 -0700349 for (const sp<RenderNode>& node : nodes) {
350 if (node->nothingToDraw()) continue;
Chris Craik8d1f2122015-11-24 16:40:09 -0800351 node->computeOrdering();
Chris Craikb565df12015-10-05 13:00:52 -0700352
Chris Craik0b7e8242015-10-28 16:50:44 -0700353 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
354 deferNodePropsAndOps(*node);
355 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700356 }
357}
358
Chris Craik818c9fb2015-10-23 14:33:42 -0700359void OpReorderer::onViewportInitialized() {}
360
361void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
362
Chris Craik0b7e8242015-10-28 16:50:44 -0700363void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800364 const RenderProperties& properties = node.properties();
365 const Outline& outline = properties.getOutline();
366 if (properties.getAlpha() <= 0
367 || (outline.getShouldClip() && outline.isEmpty())
368 || properties.getScaleX() == 0
369 || properties.getScaleY() == 0) {
370 return; // rejected
371 }
372
373 if (properties.getLeft() != 0 || properties.getTop() != 0) {
374 mCanvasState.translate(properties.getLeft(), properties.getTop());
375 }
376 if (properties.getStaticMatrix()) {
377 mCanvasState.concatMatrix(*properties.getStaticMatrix());
378 } else if (properties.getAnimationMatrix()) {
379 mCanvasState.concatMatrix(*properties.getAnimationMatrix());
380 }
381 if (properties.hasTransformMatrix()) {
382 if (properties.isTransformTranslateOnly()) {
383 mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
384 } else {
385 mCanvasState.concatMatrix(*properties.getTransformMatrix());
386 }
387 }
388
389 const int width = properties.getWidth();
390 const int height = properties.getHeight();
391
392 Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
393 const bool isLayer = properties.effectiveLayerType() != LayerType::None;
394 int clipFlags = properties.getClippingFlags();
395 if (properties.getAlpha() < 1) {
396 if (isLayer) {
397 clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
398 }
399 if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
400 // simply scale rendering content's alpha
401 mCanvasState.scaleAlpha(properties.getAlpha());
402 } else {
403 // schedule saveLayer by initializing saveLayerBounds
404 saveLayerBounds.set(0, 0, width, height);
405 if (clipFlags) {
406 properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
407 clipFlags = 0; // all clipping done by savelayer
408 }
409 }
410
411 if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
412 // pretend alpha always causes savelayer to warn about
413 // performance problem affecting old versions
414 ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
415 }
416 }
417 if (clipFlags) {
418 Rect clipRect;
419 properties.getClippingRectForFlags(clipFlags, &clipRect);
420 mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
421 SkRegion::kIntersect_Op);
422 }
423
424 if (properties.getRevealClip().willClip()) {
425 Rect bounds;
426 properties.getRevealClip().getBounds(&bounds);
427 mCanvasState.setClippingRoundRect(mAllocator,
428 bounds, properties.getRevealClip().getRadius());
429 } else if (properties.getOutline().willClip()) {
430 mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
431 }
432
433 if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
434 // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
Chris Craik0b7e8242015-10-28 16:50:44 -0700435 if (node.getLayer()) {
436 // HW layer
437 LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
438 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
439 if (bakedOpState) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800440 // Node's layer already deferred, schedule it to render into parent layer
Chris Craik0b7e8242015-10-28 16:50:44 -0700441 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
442 }
Chris Craik8ecf41c2015-11-16 10:27:59 -0800443 } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
444 // draw DisplayList contents within temporary, since persisted layer could not be used.
445 // (temp layers are clipped to viewport, since they don't persist offscreen content)
446 SkPaint saveLayerPaint;
447 saveLayerPaint.setAlpha(properties.getAlpha());
448 onBeginLayerOp(*new (mAllocator) BeginLayerOp(
449 saveLayerBounds,
450 Matrix4::identity(),
451 saveLayerBounds,
452 &saveLayerPaint));
Chris Craik8d1f2122015-11-24 16:40:09 -0800453 deferNodeOps(node);
Chris Craik8ecf41c2015-11-16 10:27:59 -0800454 onEndLayerOp(*new (mAllocator) EndLayerOp());
Chris Craik0b7e8242015-10-28 16:50:44 -0700455 } else {
Chris Craik8d1f2122015-11-24 16:40:09 -0800456 deferNodeOps(node);
Chris Craik0b7e8242015-10-28 16:50:44 -0700457 }
458 }
459}
460
Chris Craik161f54b2015-11-05 11:08:52 -0800461typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
462
463template <typename V>
464static void buildZSortedChildList(V* zTranslatedNodes,
465 const DisplayList& displayList, const DisplayList::Chunk& chunk) {
466 if (chunk.beginChildIndex == chunk.endChildIndex) return;
467
468 for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
469 RenderNodeOp* childOp = displayList.getChildren()[i];
470 RenderNode* child = childOp->renderNode;
471 float childZ = child->properties().getZ();
472
473 if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
474 zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
475 childOp->skipInOrderDraw = true;
476 } else if (!child->properties().getProjectBackwards()) {
477 // regular, in order drawing DisplayList
478 childOp->skipInOrderDraw = false;
479 }
480 }
481
482 // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
483 std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
484}
485
486template <typename V>
487static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
488 for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
489 if (zTranslatedNodes[i].key >= 0.0f) return i;
490 }
491 return zTranslatedNodes.size();
492}
493
494template <typename V>
495void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
496 const int size = zTranslatedNodes.size();
497 if (size == 0
498 || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
499 || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
500 // no 3d children to draw
501 return;
502 }
503
504 /**
505 * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
506 * with very similar Z heights to draw together.
507 *
508 * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
509 * underneath both, and neither's shadow is drawn on top of the other.
510 */
511 const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
512 size_t drawIndex, shadowIndex, endIndex;
513 if (mode == ChildrenSelectMode::Negative) {
514 drawIndex = 0;
515 endIndex = nonNegativeIndex;
516 shadowIndex = endIndex; // draw no shadows
517 } else {
518 drawIndex = nonNegativeIndex;
519 endIndex = size;
520 shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
521 }
522
523 float lastCasterZ = 0.0f;
524 while (shadowIndex < endIndex || drawIndex < endIndex) {
525 if (shadowIndex < endIndex) {
526 const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
527 const float casterZ = zTranslatedNodes[shadowIndex].key;
528 // attempt to render the shadow if the caster about to be drawn is its caster,
529 // OR if its caster's Z value is similar to the previous potential caster
530 if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
531 deferShadow(*casterNodeOp);
532
533 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
534 shadowIndex++;
535 continue;
536 }
537 }
538
539 const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
540 deferRenderNodeOp(*childOp);
541 drawIndex++;
542 }
543}
544
545void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
Chris Craikd3daa312015-11-06 10:59:56 -0800546 auto& node = *casterNodeOp.renderNode;
547 auto& properties = node.properties();
548
549 if (properties.getAlpha() <= 0.0f
550 || properties.getOutline().getAlpha() <= 0.0f
551 || !properties.getOutline().getPath()
552 || properties.getScaleX() == 0
553 || properties.getScaleY() == 0) {
554 // no shadow to draw
555 return;
556 }
557
558 const SkPath* casterOutlinePath = properties.getOutline().getPath();
559 const SkPath* revealClipPath = properties.getRevealClip().getPath();
560 if (revealClipPath && revealClipPath->isEmpty()) return;
561
562 float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
563
564 // holds temporary SkPath to store the result of intersections
565 SkPath* frameAllocatedPath = nullptr;
566 const SkPath* casterPath = casterOutlinePath;
567
568 // intersect the shadow-casting path with the reveal, if present
569 if (revealClipPath) {
570 frameAllocatedPath = createFrameAllocatedPath();
571
572 Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
573 casterPath = frameAllocatedPath;
574 }
575
576 // intersect the shadow-casting path with the clipBounds, if present
577 if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
578 if (!frameAllocatedPath) {
579 frameAllocatedPath = createFrameAllocatedPath();
580 }
581 Rect clipBounds;
582 properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
583 SkPath clipBoundsPath;
584 clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
585 clipBounds.right, clipBounds.bottom);
586
587 Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
588 casterPath = frameAllocatedPath;
589 }
590
591 ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
Chris Craik98787e62015-11-13 10:55:30 -0800592 mCanvasState.getLocalClipBounds(),
593 mCanvasState.currentSnapshot()->getRelativeLightCenter());
Chris Craikd3daa312015-11-06 10:59:56 -0800594 BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
595 mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
596 if (CC_LIKELY(bakedOpState)) {
597 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
598 }
Chris Craik161f54b2015-11-05 11:08:52 -0800599}
Chris Craikd3daa312015-11-06 10:59:56 -0800600
Chris Craik8d1f2122015-11-24 16:40:09 -0800601void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
602 const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
603 int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
604
605 // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
606 const DisplayList& displayList = *(renderNode.getDisplayList());
607
608 const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
609 const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
610 const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
611
612 // Transform renderer to match background we're projecting onto
613 // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
614 mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
615
616 // If the projection receiver has an outline, we mask projected content to it
617 // (which we know, apriori, are all tessellated paths)
618 mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
619
620 // draw projected nodes
621 for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
622 RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
623
624 int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
625 mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
626 deferRenderNodeOp(*childOp);
627 mCanvasState.restoreToCount(restoreTo);
628 }
629
630 mCanvasState.restoreToCount(count);
631}
632
Chris Craikb565df12015-10-05 13:00:52 -0700633/**
634 * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
635 *
Chris Craik8d1f2122015-11-24 16:40:09 -0800636 * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
637 * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
Chris Craikb565df12015-10-05 13:00:52 -0700638 */
Chris Craik6fe991e52015-10-20 09:39:42 -0700639#define OP_RECEIVER(Type) \
Chris Craikb565df12015-10-05 13:00:52 -0700640 [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
Chris Craik8d1f2122015-11-24 16:40:09 -0800641void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
Chris Craikb565df12015-10-05 13:00:52 -0700642 static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
Chris Craik6fe991e52015-10-20 09:39:42 -0700643 MAP_OPS(OP_RECEIVER)
Chris Craikb565df12015-10-05 13:00:52 -0700644 };
Chris Craik8d1f2122015-11-24 16:40:09 -0800645
646 // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
647 const DisplayList& displayList = *(renderNode.getDisplayList());
Chris Craikb36af872015-10-16 14:23:12 -0700648 for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
Chris Craik161f54b2015-11-05 11:08:52 -0800649 FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
650 buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
651
652 defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700653 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
Chris Craikb36af872015-10-16 14:23:12 -0700654 const RecordedOp* op = displayList.getOps()[opIndex];
Chris Craikb565df12015-10-05 13:00:52 -0700655 receivers[op->opId](*this, *op);
Chris Craik8d1f2122015-11-24 16:40:09 -0800656
657 if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
658 && displayList.projectionReceiveIndex >= 0
659 && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
660 deferProjectedChildren(renderNode);
661 }
Chris Craikb565df12015-10-05 13:00:52 -0700662 }
Chris Craik161f54b2015-11-05 11:08:52 -0800663 defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700664 }
665}
666
Chris Craik161f54b2015-11-05 11:08:52 -0800667void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
668 if (op.renderNode->nothingToDraw()) return;
Chris Craik6fe991e52015-10-20 09:39:42 -0700669 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craikb565df12015-10-05 13:00:52 -0700670
671 // apply state from RecordedOp
672 mCanvasState.concatMatrix(op.localMatrix);
673 mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
674 op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
675
Chris Craik0b7e8242015-10-28 16:50:44 -0700676 // then apply state from node properties, and defer ops
677 deferNodePropsAndOps(*op.renderNode);
678
Chris Craik6fe991e52015-10-20 09:39:42 -0700679 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700680}
681
Chris Craik161f54b2015-11-05 11:08:52 -0800682void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
683 if (!op.skipInOrderDraw) {
684 deferRenderNodeOp(op);
685 }
686}
687
Chris Craikb565df12015-10-05 13:00:52 -0700688static batchid_t tessellatedBatchId(const SkPaint& paint) {
689 return paint.getPathEffect()
690 ? OpBatchType::AlphaMaskTexture
691 : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
692}
693
694void OpReorderer::onBitmapOp(const BitmapOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700695 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700696 if (!bakedStateOp) return; // quick rejected
697
698 mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
699 // TODO: AssetAtlas
Chris Craik6fe991e52015-10-20 09:39:42 -0700700 currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
Chris Craikb565df12015-10-05 13:00:52 -0700701}
702
Chris Craika1717272015-11-19 13:02:43 -0800703void OpReorderer::onLinesOp(const LinesOp& op) {
704 BakedOpState* bakedStateOp = tryBakeOpState(op);
705 if (!bakedStateOp) return; // quick rejected
706 currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
707
708}
709
Chris Craikb565df12015-10-05 13:00:52 -0700710void OpReorderer::onRectOp(const RectOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700711 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700712 if (!bakedStateOp) return; // quick rejected
Chris Craik6fe991e52015-10-20 09:39:42 -0700713 currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
Chris Craikb565df12015-10-05 13:00:52 -0700714}
715
716void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700717 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700718 if (!bakedStateOp) return; // quick rejected
Chris Craik6fe991e52015-10-20 09:39:42 -0700719 currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
Chris Craikb565df12015-10-05 13:00:52 -0700720}
721
Chris Craika1717272015-11-19 13:02:43 -0800722void OpReorderer::onTextOp(const TextOp& op) {
723 BakedOpState* bakedStateOp = tryBakeOpState(op);
724 if (!bakedStateOp) return; // quick rejected
725
726 // TODO: better handling of shader (since we won't care about color then)
727 batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
728 ? OpBatchType::Text : OpBatchType::ColorText;
729 mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
730 currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
731}
732
Chris Craik8ecf41c2015-11-16 10:27:59 -0800733void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
734 float contentTranslateX, float contentTranslateY,
735 const Rect& repaintRect,
736 const Vector3& lightCenter,
Chris Craik0b7e8242015-10-28 16:50:44 -0700737 const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700738 mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craik818c9fb2015-10-23 14:33:42 -0700739 mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
Chris Craik6fe991e52015-10-20 09:39:42 -0700740 mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
Chris Craik98787e62015-11-13 10:55:30 -0800741 mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
Chris Craik8ecf41c2015-11-16 10:27:59 -0800742 mCanvasState.writableSnapshot()->transform->loadTranslate(
743 contentTranslateX, contentTranslateY, 0);
744 mCanvasState.writableSnapshot()->setClip(
745 repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
Chris Craik98787e62015-11-13 10:55:30 -0800746
Chris Craik8ecf41c2015-11-16 10:27:59 -0800747 // create a new layer repaint, and push its index on the stack
Chris Craik6fe991e52015-10-20 09:39:42 -0700748 mLayerStack.push_back(mLayerReorderers.size());
Chris Craik98787e62015-11-13 10:55:30 -0800749 mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700750}
751
752void OpReorderer::restoreForLayer() {
753 // restore canvas, and pop finished layer off of the stack
754 mCanvasState.restore();
755 mLayerStack.pop_back();
756}
757
758// TODO: test rejection at defer time, where the bounds become empty
759void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800760 uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
761 uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
762
763 auto previous = mCanvasState.currentSnapshot();
764 Vector3 lightCenter = previous->getRelativeLightCenter();
765
766 // Combine all transforms used to present saveLayer content:
767 // parent content transform * canvas transform * bounds offset
768 Matrix4 contentTransform(*previous->transform);
769 contentTransform.multiply(op.localMatrix);
770 contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
771
772 Matrix4 inverseContentTransform;
773 inverseContentTransform.loadInverse(contentTransform);
774
775 // map the light center into layer-relative space
776 inverseContentTransform.mapPoint3d(lightCenter);
777
778 // Clip bounds of temporary layer to parent's clip rect, so:
779 Rect saveLayerBounds(layerWidth, layerHeight);
780 // 1) transform Rect(width, height) into parent's space
781 // note: left/top offsets put in contentTransform above
782 contentTransform.mapRect(saveLayerBounds);
783 // 2) intersect with parent's clip
784 saveLayerBounds.doIntersect(previous->getRenderTargetClip());
785 // 3) and transform back
786 inverseContentTransform.mapRect(saveLayerBounds);
787 saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
788 saveLayerBounds.roundOut();
789
790 // if bounds are reduced, will clip the layer's area by reducing required bounds...
791 layerWidth = saveLayerBounds.getWidth();
792 layerHeight = saveLayerBounds.getHeight();
793 // ...and shifting drawing content to account for left/top side clipping
794 float contentTranslateX = -saveLayerBounds.left;
795 float contentTranslateY = -saveLayerBounds.top;
796
797 saveForLayer(layerWidth, layerHeight,
798 contentTranslateX, contentTranslateY,
799 Rect(layerWidth, layerHeight),
800 lightCenter,
801 &op, nullptr);
Chris Craik6fe991e52015-10-20 09:39:42 -0700802}
Chris Craikb565df12015-10-05 13:00:52 -0700803
Chris Craik6fe991e52015-10-20 09:39:42 -0700804void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700805 const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
Chris Craik6fe991e52015-10-20 09:39:42 -0700806 int finishedLayerIndex = mLayerStack.back();
Chris Craik0b7e8242015-10-28 16:50:44 -0700807
808 restoreForLayer();
Chris Craik6fe991e52015-10-20 09:39:42 -0700809
810 // record the draw operation into the previous layer's list of draw commands
811 // uses state from the associated beginLayerOp, since it has all the state needed for drawing
812 LayerOp* drawLayerOp = new (mAllocator) LayerOp(
813 beginLayerOp.unmappedBounds,
814 beginLayerOp.localMatrix,
815 beginLayerOp.localClipRect,
Chris Craik818c9fb2015-10-23 14:33:42 -0700816 beginLayerOp.paint,
Chris Craik5854b342015-10-26 15:49:56 -0700817 &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
Chris Craik6fe991e52015-10-20 09:39:42 -0700818 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
819
820 if (bakedOpState) {
821 // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
822 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
823 } else {
824 // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
825 mLayerReorderers[finishedLayerIndex].clear();
826 return;
Chris Craikb565df12015-10-05 13:00:52 -0700827 }
828}
829
Chris Craik6fe991e52015-10-20 09:39:42 -0700830void OpReorderer::onLayerOp(const LayerOp& op) {
831 LOG_ALWAYS_FATAL("unsupported");
Chris Craikb565df12015-10-05 13:00:52 -0700832}
833
Chris Craikd3daa312015-11-06 10:59:56 -0800834void OpReorderer::onShadowOp(const ShadowOp& op) {
835 LOG_ALWAYS_FATAL("unsupported");
836}
837
Chris Craikb565df12015-10-05 13:00:52 -0700838} // namespace uirenderer
839} // namespace android