blob: 96cac7eedaf06b34ff5a9e478aacac0e0a25fee2 [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;
333
Chris Craik8ecf41c2015-11-16 10:27:59 -0800334 // map current light center into RenderNode's coordinate space
335 Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
336 layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter);
337
338 saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
339 layerDamage, lightCenter, nullptr, layerNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700340
341 if (layerNode->getDisplayList()) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800342 deferDisplayList(*(layerNode->getDisplayList()));
Chris Craik0b7e8242015-10-28 16:50:44 -0700343 }
344 restoreForLayer();
345 }
346
347 // Defer Fbo0
Chris Craikb565df12015-10-05 13:00:52 -0700348 for (const sp<RenderNode>& node : nodes) {
349 if (node->nothingToDraw()) continue;
350
Chris Craik0b7e8242015-10-28 16:50:44 -0700351 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
352 deferNodePropsAndOps(*node);
353 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700354 }
355}
356
Chris Craik98787e62015-11-13 10:55:30 -0800357OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
358 const Vector3& lightCenter)
Chris Craik818c9fb2015-10-23 14:33:42 -0700359 : mCanvasState(*this) {
Chris Craikb565df12015-10-05 13:00:52 -0700360 ATRACE_NAME("prepare drawing commands");
Chris Craik98787e62015-11-13 10:55:30 -0800361 // Prepare to defer Fbo0
362 mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
363 Rect(viewportWidth, viewportHeight));
Chris Craik818c9fb2015-10-23 14:33:42 -0700364 mLayerStack.push_back(0);
Chris Craikb565df12015-10-05 13:00:52 -0700365 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
Chris Craik98787e62015-11-13 10:55:30 -0800366 0, 0, viewportWidth, viewportHeight, lightCenter);
367
Chris Craik8ecf41c2015-11-16 10:27:59 -0800368 deferDisplayList(displayList);
Chris Craikb565df12015-10-05 13:00:52 -0700369}
370
Chris Craik818c9fb2015-10-23 14:33:42 -0700371void OpReorderer::onViewportInitialized() {}
372
373void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
374
Chris Craik0b7e8242015-10-28 16:50:44 -0700375void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800376 const RenderProperties& properties = node.properties();
377 const Outline& outline = properties.getOutline();
378 if (properties.getAlpha() <= 0
379 || (outline.getShouldClip() && outline.isEmpty())
380 || properties.getScaleX() == 0
381 || properties.getScaleY() == 0) {
382 return; // rejected
383 }
384
385 if (properties.getLeft() != 0 || properties.getTop() != 0) {
386 mCanvasState.translate(properties.getLeft(), properties.getTop());
387 }
388 if (properties.getStaticMatrix()) {
389 mCanvasState.concatMatrix(*properties.getStaticMatrix());
390 } else if (properties.getAnimationMatrix()) {
391 mCanvasState.concatMatrix(*properties.getAnimationMatrix());
392 }
393 if (properties.hasTransformMatrix()) {
394 if (properties.isTransformTranslateOnly()) {
395 mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
396 } else {
397 mCanvasState.concatMatrix(*properties.getTransformMatrix());
398 }
399 }
400
401 const int width = properties.getWidth();
402 const int height = properties.getHeight();
403
404 Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
405 const bool isLayer = properties.effectiveLayerType() != LayerType::None;
406 int clipFlags = properties.getClippingFlags();
407 if (properties.getAlpha() < 1) {
408 if (isLayer) {
409 clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
410 }
411 if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
412 // simply scale rendering content's alpha
413 mCanvasState.scaleAlpha(properties.getAlpha());
414 } else {
415 // schedule saveLayer by initializing saveLayerBounds
416 saveLayerBounds.set(0, 0, width, height);
417 if (clipFlags) {
418 properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
419 clipFlags = 0; // all clipping done by savelayer
420 }
421 }
422
423 if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
424 // pretend alpha always causes savelayer to warn about
425 // performance problem affecting old versions
426 ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
427 }
428 }
429 if (clipFlags) {
430 Rect clipRect;
431 properties.getClippingRectForFlags(clipFlags, &clipRect);
432 mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
433 SkRegion::kIntersect_Op);
434 }
435
436 if (properties.getRevealClip().willClip()) {
437 Rect bounds;
438 properties.getRevealClip().getBounds(&bounds);
439 mCanvasState.setClippingRoundRect(mAllocator,
440 bounds, properties.getRevealClip().getRadius());
441 } else if (properties.getOutline().willClip()) {
442 mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
443 }
444
445 if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
446 // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
Chris Craik0b7e8242015-10-28 16:50:44 -0700447 if (node.getLayer()) {
448 // HW layer
449 LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
450 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
451 if (bakedOpState) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800452 // Node's layer already deferred, schedule it to render into parent layer
Chris Craik0b7e8242015-10-28 16:50:44 -0700453 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
454 }
Chris Craik8ecf41c2015-11-16 10:27:59 -0800455 } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
456 // draw DisplayList contents within temporary, since persisted layer could not be used.
457 // (temp layers are clipped to viewport, since they don't persist offscreen content)
458 SkPaint saveLayerPaint;
459 saveLayerPaint.setAlpha(properties.getAlpha());
460 onBeginLayerOp(*new (mAllocator) BeginLayerOp(
461 saveLayerBounds,
462 Matrix4::identity(),
463 saveLayerBounds,
464 &saveLayerPaint));
465 deferDisplayList(*(node.getDisplayList()));
466 onEndLayerOp(*new (mAllocator) EndLayerOp());
Chris Craik0b7e8242015-10-28 16:50:44 -0700467 } else {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800468 deferDisplayList(*(node.getDisplayList()));
Chris Craik0b7e8242015-10-28 16:50:44 -0700469 }
470 }
471}
472
Chris Craik161f54b2015-11-05 11:08:52 -0800473typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
474
475template <typename V>
476static void buildZSortedChildList(V* zTranslatedNodes,
477 const DisplayList& displayList, const DisplayList::Chunk& chunk) {
478 if (chunk.beginChildIndex == chunk.endChildIndex) return;
479
480 for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
481 RenderNodeOp* childOp = displayList.getChildren()[i];
482 RenderNode* child = childOp->renderNode;
483 float childZ = child->properties().getZ();
484
485 if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
486 zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
487 childOp->skipInOrderDraw = true;
488 } else if (!child->properties().getProjectBackwards()) {
489 // regular, in order drawing DisplayList
490 childOp->skipInOrderDraw = false;
491 }
492 }
493
494 // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
495 std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
496}
497
498template <typename V>
499static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
500 for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
501 if (zTranslatedNodes[i].key >= 0.0f) return i;
502 }
503 return zTranslatedNodes.size();
504}
505
506template <typename V>
507void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
508 const int size = zTranslatedNodes.size();
509 if (size == 0
510 || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
511 || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
512 // no 3d children to draw
513 return;
514 }
515
516 /**
517 * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
518 * with very similar Z heights to draw together.
519 *
520 * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
521 * underneath both, and neither's shadow is drawn on top of the other.
522 */
523 const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
524 size_t drawIndex, shadowIndex, endIndex;
525 if (mode == ChildrenSelectMode::Negative) {
526 drawIndex = 0;
527 endIndex = nonNegativeIndex;
528 shadowIndex = endIndex; // draw no shadows
529 } else {
530 drawIndex = nonNegativeIndex;
531 endIndex = size;
532 shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
533 }
534
535 float lastCasterZ = 0.0f;
536 while (shadowIndex < endIndex || drawIndex < endIndex) {
537 if (shadowIndex < endIndex) {
538 const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
539 const float casterZ = zTranslatedNodes[shadowIndex].key;
540 // attempt to render the shadow if the caster about to be drawn is its caster,
541 // OR if its caster's Z value is similar to the previous potential caster
542 if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
543 deferShadow(*casterNodeOp);
544
545 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
546 shadowIndex++;
547 continue;
548 }
549 }
550
551 const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
552 deferRenderNodeOp(*childOp);
553 drawIndex++;
554 }
555}
556
557void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
Chris Craikd3daa312015-11-06 10:59:56 -0800558 auto& node = *casterNodeOp.renderNode;
559 auto& properties = node.properties();
560
561 if (properties.getAlpha() <= 0.0f
562 || properties.getOutline().getAlpha() <= 0.0f
563 || !properties.getOutline().getPath()
564 || properties.getScaleX() == 0
565 || properties.getScaleY() == 0) {
566 // no shadow to draw
567 return;
568 }
569
570 const SkPath* casterOutlinePath = properties.getOutline().getPath();
571 const SkPath* revealClipPath = properties.getRevealClip().getPath();
572 if (revealClipPath && revealClipPath->isEmpty()) return;
573
574 float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
575
576 // holds temporary SkPath to store the result of intersections
577 SkPath* frameAllocatedPath = nullptr;
578 const SkPath* casterPath = casterOutlinePath;
579
580 // intersect the shadow-casting path with the reveal, if present
581 if (revealClipPath) {
582 frameAllocatedPath = createFrameAllocatedPath();
583
584 Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
585 casterPath = frameAllocatedPath;
586 }
587
588 // intersect the shadow-casting path with the clipBounds, if present
589 if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
590 if (!frameAllocatedPath) {
591 frameAllocatedPath = createFrameAllocatedPath();
592 }
593 Rect clipBounds;
594 properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
595 SkPath clipBoundsPath;
596 clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
597 clipBounds.right, clipBounds.bottom);
598
599 Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
600 casterPath = frameAllocatedPath;
601 }
602
603 ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
Chris Craik98787e62015-11-13 10:55:30 -0800604 mCanvasState.getLocalClipBounds(),
605 mCanvasState.currentSnapshot()->getRelativeLightCenter());
Chris Craikd3daa312015-11-06 10:59:56 -0800606 BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
607 mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
608 if (CC_LIKELY(bakedOpState)) {
609 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
610 }
Chris Craik161f54b2015-11-05 11:08:52 -0800611}
Chris Craikd3daa312015-11-06 10:59:56 -0800612
Chris Craikb565df12015-10-05 13:00:52 -0700613/**
614 * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
615 *
616 * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
617 * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
618 */
Chris Craik6fe991e52015-10-20 09:39:42 -0700619#define OP_RECEIVER(Type) \
Chris Craikb565df12015-10-05 13:00:52 -0700620 [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
Chris Craik8ecf41c2015-11-16 10:27:59 -0800621void OpReorderer::deferDisplayList(const DisplayList& displayList) {
Chris Craikb565df12015-10-05 13:00:52 -0700622 static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
Chris Craik6fe991e52015-10-20 09:39:42 -0700623 MAP_OPS(OP_RECEIVER)
Chris Craikb565df12015-10-05 13:00:52 -0700624 };
Chris Craikb36af872015-10-16 14:23:12 -0700625 for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
Chris Craik161f54b2015-11-05 11:08:52 -0800626 FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
627 buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
628
629 defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700630 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
Chris Craikb36af872015-10-16 14:23:12 -0700631 const RecordedOp* op = displayList.getOps()[opIndex];
Chris Craikb565df12015-10-05 13:00:52 -0700632 receivers[op->opId](*this, *op);
633 }
Chris Craik161f54b2015-11-05 11:08:52 -0800634 defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
Chris Craikb565df12015-10-05 13:00:52 -0700635 }
636}
637
Chris Craik161f54b2015-11-05 11:08:52 -0800638void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
639 if (op.renderNode->nothingToDraw()) return;
Chris Craik6fe991e52015-10-20 09:39:42 -0700640 int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craikb565df12015-10-05 13:00:52 -0700641
642 // apply state from RecordedOp
643 mCanvasState.concatMatrix(op.localMatrix);
644 mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
645 op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
646
Chris Craik0b7e8242015-10-28 16:50:44 -0700647 // then apply state from node properties, and defer ops
648 deferNodePropsAndOps(*op.renderNode);
649
Chris Craik6fe991e52015-10-20 09:39:42 -0700650 mCanvasState.restoreToCount(count);
Chris Craikb565df12015-10-05 13:00:52 -0700651}
652
Chris Craik161f54b2015-11-05 11:08:52 -0800653void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
654 if (!op.skipInOrderDraw) {
655 deferRenderNodeOp(op);
656 }
657}
658
Chris Craikb565df12015-10-05 13:00:52 -0700659static batchid_t tessellatedBatchId(const SkPaint& paint) {
660 return paint.getPathEffect()
661 ? OpBatchType::AlphaMaskTexture
662 : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
663}
664
665void OpReorderer::onBitmapOp(const BitmapOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700666 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700667 if (!bakedStateOp) return; // quick rejected
668
669 mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
670 // TODO: AssetAtlas
Chris Craik6fe991e52015-10-20 09:39:42 -0700671 currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
Chris Craikb565df12015-10-05 13:00:52 -0700672}
673
674void OpReorderer::onRectOp(const RectOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700675 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700676 if (!bakedStateOp) return; // quick rejected
Chris Craik6fe991e52015-10-20 09:39:42 -0700677 currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
Chris Craikb565df12015-10-05 13:00:52 -0700678}
679
680void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700681 BakedOpState* bakedStateOp = tryBakeOpState(op);
Chris Craikb565df12015-10-05 13:00:52 -0700682 if (!bakedStateOp) return; // quick rejected
Chris Craik6fe991e52015-10-20 09:39:42 -0700683 currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
Chris Craikb565df12015-10-05 13:00:52 -0700684}
685
Chris Craik8ecf41c2015-11-16 10:27:59 -0800686void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
687 float contentTranslateX, float contentTranslateY,
688 const Rect& repaintRect,
689 const Vector3& lightCenter,
Chris Craik0b7e8242015-10-28 16:50:44 -0700690 const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700691 mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
Chris Craik818c9fb2015-10-23 14:33:42 -0700692 mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
Chris Craik6fe991e52015-10-20 09:39:42 -0700693 mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
Chris Craik98787e62015-11-13 10:55:30 -0800694 mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
Chris Craik8ecf41c2015-11-16 10:27:59 -0800695 mCanvasState.writableSnapshot()->transform->loadTranslate(
696 contentTranslateX, contentTranslateY, 0);
697 mCanvasState.writableSnapshot()->setClip(
698 repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
Chris Craik98787e62015-11-13 10:55:30 -0800699
Chris Craik8ecf41c2015-11-16 10:27:59 -0800700 // create a new layer repaint, and push its index on the stack
Chris Craik6fe991e52015-10-20 09:39:42 -0700701 mLayerStack.push_back(mLayerReorderers.size());
Chris Craik98787e62015-11-13 10:55:30 -0800702 mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
Chris Craik0b7e8242015-10-28 16:50:44 -0700703}
704
705void OpReorderer::restoreForLayer() {
706 // restore canvas, and pop finished layer off of the stack
707 mCanvasState.restore();
708 mLayerStack.pop_back();
709}
710
711// TODO: test rejection at defer time, where the bounds become empty
712void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
Chris Craik8ecf41c2015-11-16 10:27:59 -0800713 uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
714 uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
715
716 auto previous = mCanvasState.currentSnapshot();
717 Vector3 lightCenter = previous->getRelativeLightCenter();
718
719 // Combine all transforms used to present saveLayer content:
720 // parent content transform * canvas transform * bounds offset
721 Matrix4 contentTransform(*previous->transform);
722 contentTransform.multiply(op.localMatrix);
723 contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
724
725 Matrix4 inverseContentTransform;
726 inverseContentTransform.loadInverse(contentTransform);
727
728 // map the light center into layer-relative space
729 inverseContentTransform.mapPoint3d(lightCenter);
730
731 // Clip bounds of temporary layer to parent's clip rect, so:
732 Rect saveLayerBounds(layerWidth, layerHeight);
733 // 1) transform Rect(width, height) into parent's space
734 // note: left/top offsets put in contentTransform above
735 contentTransform.mapRect(saveLayerBounds);
736 // 2) intersect with parent's clip
737 saveLayerBounds.doIntersect(previous->getRenderTargetClip());
738 // 3) and transform back
739 inverseContentTransform.mapRect(saveLayerBounds);
740 saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
741 saveLayerBounds.roundOut();
742
743 // if bounds are reduced, will clip the layer's area by reducing required bounds...
744 layerWidth = saveLayerBounds.getWidth();
745 layerHeight = saveLayerBounds.getHeight();
746 // ...and shifting drawing content to account for left/top side clipping
747 float contentTranslateX = -saveLayerBounds.left;
748 float contentTranslateY = -saveLayerBounds.top;
749
750 saveForLayer(layerWidth, layerHeight,
751 contentTranslateX, contentTranslateY,
752 Rect(layerWidth, layerHeight),
753 lightCenter,
754 &op, nullptr);
Chris Craik6fe991e52015-10-20 09:39:42 -0700755}
Chris Craikb565df12015-10-05 13:00:52 -0700756
Chris Craik6fe991e52015-10-20 09:39:42 -0700757void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
Chris Craik6fe991e52015-10-20 09:39:42 -0700758 const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
Chris Craik6fe991e52015-10-20 09:39:42 -0700759 int finishedLayerIndex = mLayerStack.back();
Chris Craik0b7e8242015-10-28 16:50:44 -0700760
761 restoreForLayer();
Chris Craik6fe991e52015-10-20 09:39:42 -0700762
763 // record the draw operation into the previous layer's list of draw commands
764 // uses state from the associated beginLayerOp, since it has all the state needed for drawing
765 LayerOp* drawLayerOp = new (mAllocator) LayerOp(
766 beginLayerOp.unmappedBounds,
767 beginLayerOp.localMatrix,
768 beginLayerOp.localClipRect,
Chris Craik818c9fb2015-10-23 14:33:42 -0700769 beginLayerOp.paint,
Chris Craik5854b342015-10-26 15:49:56 -0700770 &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
Chris Craik6fe991e52015-10-20 09:39:42 -0700771 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
772
773 if (bakedOpState) {
774 // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
775 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
776 } else {
777 // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
778 mLayerReorderers[finishedLayerIndex].clear();
779 return;
Chris Craikb565df12015-10-05 13:00:52 -0700780 }
781}
782
Chris Craik6fe991e52015-10-20 09:39:42 -0700783void OpReorderer::onLayerOp(const LayerOp& op) {
784 LOG_ALWAYS_FATAL("unsupported");
Chris Craikb565df12015-10-05 13:00:52 -0700785}
786
Chris Craikd3daa312015-11-06 10:59:56 -0800787void OpReorderer::onShadowOp(const ShadowOp& op) {
788 LOG_ALWAYS_FATAL("unsupported");
789}
790
Chris Craikb565df12015-10-05 13:00:52 -0700791} // namespace uirenderer
792} // namespace android