• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "FrameBuilder.h"
18 
19 #include "DeferredLayerUpdater.h"
20 #include "LayerUpdateQueue.h"
21 #include "RenderNode.h"
22 #include "VectorDrawable.h"
23 #include "renderstate/OffscreenBufferPool.h"
24 #include "hwui/Canvas.h"
25 #include "utils/FatVector.h"
26 #include "utils/PaintUtils.h"
27 #include "utils/TraceUtils.h"
28 
29 #include <SkPathOps.h>
30 #include <utils/TypeHelpers.h>
31 
32 namespace android {
33 namespace uirenderer {
34 
FrameBuilder(const SkRect & clip,uint32_t viewportWidth,uint32_t viewportHeight,const LightGeometry & lightGeometry,Caches & caches)35 FrameBuilder::FrameBuilder(const SkRect& clip,
36         uint32_t viewportWidth, uint32_t viewportHeight,
37         const LightGeometry& lightGeometry, Caches& caches)
38         : mStdAllocator(mAllocator)
39         , mLayerBuilders(mStdAllocator)
40         , mLayerStack(mStdAllocator)
41         , mCanvasState(*this)
42         , mCaches(caches)
43         , mLightRadius(lightGeometry.radius)
44         , mDrawFbo0(true) {
45 
46     // Prepare to defer Fbo0
47     auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip));
48     mLayerBuilders.push_back(fbo0);
49     mLayerStack.push_back(0);
50     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
51             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
52             lightGeometry.center);
53 }
54 
FrameBuilder(const LayerUpdateQueue & layers,const LightGeometry & lightGeometry,Caches & caches)55 FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers,
56         const LightGeometry& lightGeometry, Caches& caches)
57         : mStdAllocator(mAllocator)
58         , mLayerBuilders(mStdAllocator)
59         , mLayerStack(mStdAllocator)
60         , mCanvasState(*this)
61         , mCaches(caches)
62         , mLightRadius(lightGeometry.radius)
63         , mDrawFbo0(false) {
64     // TODO: remove, with each layer on its own save stack
65 
66     // Prepare to defer Fbo0 (which will be empty)
67     auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1));
68     mLayerBuilders.push_back(fbo0);
69     mLayerStack.push_back(0);
70     mCanvasState.initializeSaveStack(1, 1,
71             0, 0, 1, 1,
72             lightGeometry.center);
73 
74     deferLayers(layers);
75 }
76 
deferLayers(const LayerUpdateQueue & layers)77 void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) {
78     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
79     // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse)
80     for (int i = layers.entries().size() - 1; i >= 0; i--) {
81         RenderNode* layerNode = layers.entries()[i].renderNode.get();
82         // only schedule repaint if node still on layer - possible it may have been
83         // removed during a dropped frame, but layers may still remain scheduled so
84         // as not to lose info on what portion is damaged
85         OffscreenBuffer* layer = layerNode->getLayer();
86         if (CC_LIKELY(layer)) {
87             ATRACE_FORMAT("Optimize HW Layer DisplayList %s %ux%u",
88                     layerNode->getName(), layerNode->getWidth(), layerNode->getHeight());
89 
90             Rect layerDamage = layers.entries()[i].damage;
91             // TODO: ensure layer damage can't be larger than layer
92             layerDamage.doIntersect(0, 0, layer->viewportWidth, layer->viewportHeight);
93             layerNode->computeOrdering();
94 
95             // map current light center into RenderNode's coordinate space
96             Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
97             layer->inverseTransformInWindow.mapPoint3d(lightCenter);
98 
99             saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
100                     layerDamage, lightCenter, nullptr, layerNode);
101 
102             if (layerNode->getDisplayList()) {
103                 deferNodeOps(*layerNode);
104             }
105             restoreForLayer();
106         }
107     }
108 }
109 
deferRenderNode(RenderNode & renderNode)110 void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
111     renderNode.computeOrdering();
112 
113     mCanvasState.save(SaveFlags::MatrixClip);
114     deferNodePropsAndOps(renderNode);
115     mCanvasState.restore();
116 }
117 
deferRenderNode(float tx,float ty,Rect clipRect,RenderNode & renderNode)118 void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) {
119     renderNode.computeOrdering();
120 
121     mCanvasState.save(SaveFlags::MatrixClip);
122     mCanvasState.translate(tx, ty);
123     mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
124             SkClipOp::kIntersect);
125     deferNodePropsAndOps(renderNode);
126     mCanvasState.restore();
127 }
128 
nodeBounds(RenderNode & node)129 static Rect nodeBounds(RenderNode& node) {
130     auto& props = node.properties();
131     return Rect(props.getLeft(), props.getTop(),
132             props.getRight(), props.getBottom());
133 }
134 
deferRenderNodeScene(const std::vector<sp<RenderNode>> & nodes,const Rect & contentDrawBounds)135 void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
136         const Rect& contentDrawBounds) {
137     if (nodes.size() < 1) return;
138     if (nodes.size() == 1) {
139         if (!nodes[0]->nothingToDraw()) {
140             deferRenderNode(*nodes[0]);
141         }
142         return;
143     }
144     // It there are multiple render nodes, they are laid out as follows:
145     // #0 - backdrop (content + caption)
146     // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop)
147     // #2 - additional overlay nodes
148     // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
149     // resizing however it might become partially visible. The following render loop will crop the
150     // backdrop against the content and draw the remaining part of it. It will then draw the content
151     // cropped to the backdrop (since that indicates a shrinking of the window).
152     //
153     // Additional nodes will be drawn on top with no particular clipping semantics.
154 
155     // Usually the contents bounds should be mContentDrawBounds - however - we will
156     // move it towards the fixed edge to give it a more stable appearance (for the moment).
157     // If there is no content bounds we ignore the layering as stated above and start with 2.
158 
159     // Backdrop bounds in render target space
160     const Rect backdrop = nodeBounds(*nodes[0]);
161 
162     // Bounds that content will fill in render target space (note content node bounds may be bigger)
163     Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight());
164     content.translate(backdrop.left, backdrop.top);
165     if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) {
166         // Content doesn't entirely overlap backdrop, so fill around content (right/bottom)
167 
168         // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to
169         // also fill left/top. Currently, both 2up and freeform position content at the top/left of
170         // the backdrop, so this isn't necessary.
171         if (content.right < backdrop.right) {
172             // draw backdrop to right side of content
173             deferRenderNode(0, 0, Rect(content.right, backdrop.top,
174                     backdrop.right, backdrop.bottom), *nodes[0]);
175         }
176         if (content.bottom < backdrop.bottom) {
177             // draw backdrop to bottom of content
178             // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill
179             deferRenderNode(0, 0, Rect(content.left, content.bottom,
180                     content.right, backdrop.bottom), *nodes[0]);
181         }
182     }
183 
184     if (!nodes[1]->nothingToDraw()) {
185         if (!backdrop.isEmpty()) {
186             // content node translation to catch up with backdrop
187             float dx = contentDrawBounds.left - backdrop.left;
188             float dy = contentDrawBounds.top - backdrop.top;
189 
190             Rect contentLocalClip = backdrop;
191             contentLocalClip.translate(dx, dy);
192             deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
193         } else {
194             deferRenderNode(*nodes[1]);
195         }
196     }
197 
198     // remaining overlay nodes, simply defer
199     for (size_t index = 2; index < nodes.size(); index++) {
200         if (!nodes[index]->nothingToDraw()) {
201             deferRenderNode(*nodes[index]);
202         }
203     }
204 }
205 
onViewportInitialized()206 void FrameBuilder::onViewportInitialized() {}
207 
onSnapshotRestored(const Snapshot & removed,const Snapshot & restored)208 void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
209 
deferNodePropsAndOps(RenderNode & node)210 void FrameBuilder::deferNodePropsAndOps(RenderNode& node) {
211     const RenderProperties& properties = node.properties();
212     const Outline& outline = properties.getOutline();
213     if (properties.getAlpha() <= 0
214             || (outline.getShouldClip() && outline.isEmpty())
215             || properties.getScaleX() == 0
216             || properties.getScaleY() == 0) {
217         return; // rejected
218     }
219 
220     if (properties.getLeft() != 0 || properties.getTop() != 0) {
221         mCanvasState.translate(properties.getLeft(), properties.getTop());
222     }
223     if (properties.getStaticMatrix()) {
224         mCanvasState.concatMatrix(*properties.getStaticMatrix());
225     } else if (properties.getAnimationMatrix()) {
226         mCanvasState.concatMatrix(*properties.getAnimationMatrix());
227     }
228     if (properties.hasTransformMatrix()) {
229         if (properties.isTransformTranslateOnly()) {
230             mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
231         } else {
232             mCanvasState.concatMatrix(*properties.getTransformMatrix());
233         }
234     }
235 
236     const int width = properties.getWidth();
237     const int height = properties.getHeight();
238 
239     Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
240     const bool isLayer = properties.effectiveLayerType() != LayerType::None;
241     int clipFlags = properties.getClippingFlags();
242     if (properties.getAlpha() < 1) {
243         if (isLayer) {
244             clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
245         }
246         if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
247             // simply scale rendering content's alpha
248             mCanvasState.scaleAlpha(properties.getAlpha());
249         } else {
250             // schedule saveLayer by initializing saveLayerBounds
251             saveLayerBounds.set(0, 0, width, height);
252             if (clipFlags) {
253                 properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
254                 clipFlags = 0; // all clipping done by savelayer
255             }
256         }
257 
258         if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
259             // pretend alpha always causes savelayer to warn about
260             // performance problem affecting old versions
261             ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
262         }
263     }
264     if (clipFlags) {
265         Rect clipRect;
266         properties.getClippingRectForFlags(clipFlags, &clipRect);
267         mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
268                 SkClipOp::kIntersect);
269     }
270 
271     if (properties.getRevealClip().willClip()) {
272         Rect bounds;
273         properties.getRevealClip().getBounds(&bounds);
274         mCanvasState.setClippingRoundRect(mAllocator,
275                 bounds, properties.getRevealClip().getRadius());
276     } else if (properties.getOutline().willClip()) {
277         mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
278     }
279 
280     bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty()
281             || (properties.getClipToBounds()
282                     && mCanvasState.quickRejectConservative(0, 0, width, height));
283     if (!quickRejected) {
284         // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
285         if (node.getLayer()) {
286             // HW layer
287             LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node);
288             BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
289             if (bakedOpState) {
290                 // Node's layer already deferred, schedule it to render into parent layer
291                 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
292             }
293         } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
294             // draw DisplayList contents within temporary, since persisted layer could not be used.
295             // (temp layers are clipped to viewport, since they don't persist offscreen content)
296             SkPaint saveLayerPaint;
297             saveLayerPaint.setAlpha(properties.getAlpha());
298             deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>(
299                     saveLayerBounds,
300                     Matrix4::identity(),
301                     nullptr, // no record-time clip - need only respect defer-time one
302                     &saveLayerPaint));
303             deferNodeOps(node);
304             deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>());
305         } else {
306             deferNodeOps(node);
307         }
308     }
309 }
310 
311 typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
312 
313 template <typename V>
buildZSortedChildList(V * zTranslatedNodes,const DisplayList & displayList,const DisplayList::Chunk & chunk)314 static void buildZSortedChildList(V* zTranslatedNodes,
315         const DisplayList& displayList, const DisplayList::Chunk& chunk) {
316     if (chunk.beginChildIndex == chunk.endChildIndex) return;
317 
318     for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
319         RenderNodeOp* childOp = displayList.getChildren()[i];
320         RenderNode* child = childOp->renderNode;
321         float childZ = child->properties().getZ();
322 
323         if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
324             zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
325             childOp->skipInOrderDraw = true;
326         } else if (!child->properties().getProjectBackwards()) {
327             // regular, in order drawing DisplayList
328             childOp->skipInOrderDraw = false;
329         }
330     }
331 
332     // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
333     std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
334 }
335 
336 template <typename V>
findNonNegativeIndex(const V & zTranslatedNodes)337 static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
338     for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
339         if (zTranslatedNodes[i].key >= 0.0f) return i;
340     }
341     return zTranslatedNodes.size();
342 }
343 
344 template <typename V>
defer3dChildren(const ClipBase * reorderClip,ChildrenSelectMode mode,const V & zTranslatedNodes)345 void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
346         const V& zTranslatedNodes) {
347     const int size = zTranslatedNodes.size();
348     if (size == 0
349             || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
350             || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
351         // no 3d children to draw
352         return;
353     }
354 
355     /**
356      * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
357      * with very similar Z heights to draw together.
358      *
359      * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
360      * underneath both, and neither's shadow is drawn on top of the other.
361      */
362     const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
363     size_t drawIndex, shadowIndex, endIndex;
364     if (mode == ChildrenSelectMode::Negative) {
365         drawIndex = 0;
366         endIndex = nonNegativeIndex;
367         shadowIndex = endIndex; // draw no shadows
368     } else {
369         drawIndex = nonNegativeIndex;
370         endIndex = size;
371         shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
372     }
373 
374     float lastCasterZ = 0.0f;
375     while (shadowIndex < endIndex || drawIndex < endIndex) {
376         if (shadowIndex < endIndex) {
377             const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
378             const float casterZ = zTranslatedNodes[shadowIndex].key;
379             // attempt to render the shadow if the caster about to be drawn is its caster,
380             // OR if its caster's Z value is similar to the previous potential caster
381             if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
382                 deferShadow(reorderClip, *casterNodeOp);
383 
384                 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
385                 shadowIndex++;
386                 continue;
387             }
388         }
389 
390         const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
391         deferRenderNodeOpImpl(*childOp);
392         drawIndex++;
393     }
394 }
395 
deferShadow(const ClipBase * reorderClip,const RenderNodeOp & casterNodeOp)396 void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) {
397     auto& node = *casterNodeOp.renderNode;
398     auto& properties = node.properties();
399 
400     if (properties.getAlpha() <= 0.0f
401             || properties.getOutline().getAlpha() <= 0.0f
402             || !properties.getOutline().getPath()
403             || properties.getScaleX() == 0
404             || properties.getScaleY() == 0) {
405         // no shadow to draw
406         return;
407     }
408 
409     const SkPath* casterOutlinePath = properties.getOutline().getPath();
410     const SkPath* revealClipPath = properties.getRevealClip().getPath();
411     if (revealClipPath && revealClipPath->isEmpty()) return;
412 
413     float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
414 
415     // holds temporary SkPath to store the result of intersections
416     SkPath* frameAllocatedPath = nullptr;
417     const SkPath* casterPath = casterOutlinePath;
418 
419     // intersect the shadow-casting path with the reveal, if present
420     if (revealClipPath) {
421         frameAllocatedPath = createFrameAllocatedPath();
422 
423         Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
424         casterPath = frameAllocatedPath;
425     }
426 
427     // intersect the shadow-casting path with the clipBounds, if present
428     if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
429         if (!frameAllocatedPath) {
430             frameAllocatedPath = createFrameAllocatedPath();
431         }
432         Rect clipBounds;
433         properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
434         SkPath clipBoundsPath;
435         clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
436                 clipBounds.right, clipBounds.bottom);
437 
438         Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
439         casterPath = frameAllocatedPath;
440     }
441 
442     // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk
443     int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
444     mCanvasState.writableSnapshot()->applyClip(reorderClip,
445             *mCanvasState.currentSnapshot()->transform);
446     if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) {
447         Matrix4 shadowMatrixXY(casterNodeOp.localMatrix);
448         Matrix4 shadowMatrixZ(casterNodeOp.localMatrix);
449         node.applyViewPropertyTransforms(shadowMatrixXY, false);
450         node.applyViewPropertyTransforms(shadowMatrixZ, true);
451 
452         sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask(
453                 mCanvasState.currentTransform(),
454                 mCanvasState.getLocalClipBounds(),
455                 casterAlpha >= 1.0f,
456                 casterPath,
457                 &shadowMatrixXY, &shadowMatrixZ,
458                 mCanvasState.currentSnapshot()->getRelativeLightCenter(),
459                 mLightRadius);
460         ShadowOp* shadowOp = mAllocator.create<ShadowOp>(task, casterAlpha);
461         BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
462                 mAllocator, *mCanvasState.writableSnapshot(), shadowOp);
463         if (CC_LIKELY(bakedOpState)) {
464             currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
465         }
466     }
467     mCanvasState.restoreToCount(restoreTo);
468 }
469 
deferProjectedChildren(const RenderNode & renderNode)470 void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
471     int count = mCanvasState.save(SaveFlags::MatrixClip);
472     const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
473 
474     SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy
475     if (projectionReceiverOutline) {
476         // transform the mask for this projector into render target space
477         // TODO: consider combining both transforms by stashing transform instead of applying
478         SkMatrix skCurrentTransform;
479         mCanvasState.currentTransform()->copyTo(skCurrentTransform);
480         projectionReceiverOutline->transform(
481                 skCurrentTransform,
482                 &transformedMaskPath);
483         mCanvasState.setProjectionPathMask(&transformedMaskPath);
484     }
485 
486     for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
487         RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
488         RenderNode& childNode = *childOp->renderNode;
489 
490         // Draw child if it has content, but ignore state in childOp - matrix already applied to
491         // transformFromCompositingAncestor, and record-time clip is ignored when projecting
492         if (!childNode.nothingToDraw()) {
493             int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
494 
495             // Apply transform between ancestor and projected descendant
496             mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
497 
498             deferNodePropsAndOps(childNode);
499 
500             mCanvasState.restoreToCount(restoreTo);
501         }
502     }
503     mCanvasState.restoreToCount(count);
504 }
505 
506 /**
507  * Used to define a list of lambdas referencing private FrameBuilder::onXX::defer() methods.
508  *
509  * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
510  * E.g. a BitmapOp op then would be dispatched to FrameBuilder::onBitmapOp(const BitmapOp&)
511  */
512 #define OP_RECEIVER(Type) \
513         [](FrameBuilder& frameBuilder, const RecordedOp& op) { frameBuilder.defer##Type(static_cast<const Type&>(op)); },
deferNodeOps(const RenderNode & renderNode)514 void FrameBuilder::deferNodeOps(const RenderNode& renderNode) {
515     typedef void (*OpDispatcher) (FrameBuilder& frameBuilder, const RecordedOp& op);
516     static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER);
517 
518     // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
519     const DisplayList& displayList = *(renderNode.getDisplayList());
520     for (auto& chunk : displayList.getChunks()) {
521         FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
522         buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
523 
524         defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes);
525         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
526             const RecordedOp* op = displayList.getOps()[opIndex];
527             receivers[op->opId](*this, *op);
528 
529             if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
530                     && displayList.projectionReceiveIndex >= 0
531                     && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
532                 deferProjectedChildren(renderNode);
533             }
534         }
535         defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
536     }
537 }
538 
deferRenderNodeOpImpl(const RenderNodeOp & op)539 void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) {
540     if (op.renderNode->nothingToDraw()) return;
541     int count = mCanvasState.save(SaveFlags::MatrixClip);
542 
543     // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
544     mCanvasState.writableSnapshot()->applyClip(op.localClip,
545             *mCanvasState.currentSnapshot()->transform);
546     mCanvasState.concatMatrix(op.localMatrix);
547 
548     // then apply state from node properties, and defer ops
549     deferNodePropsAndOps(*op.renderNode);
550 
551     mCanvasState.restoreToCount(count);
552 }
553 
deferRenderNodeOp(const RenderNodeOp & op)554 void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) {
555     if (!op.skipInOrderDraw) {
556         deferRenderNodeOpImpl(op);
557     }
558 }
559 
560 /**
561  * Defers an unmergeable, strokeable op, accounting correctly
562  * for paint's style on the bounds being computed.
563  */
deferStrokeableOp(const RecordedOp & op,batchid_t batchId,BakedOpState::StrokeBehavior strokeBehavior,bool expandForPathTexture)564 BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
565         BakedOpState::StrokeBehavior strokeBehavior, bool expandForPathTexture) {
566     // Note: here we account for stroke when baking the op
567     BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
568             mAllocator, *mCanvasState.writableSnapshot(), op,
569             strokeBehavior, expandForPathTexture);
570     if (!bakedState) return nullptr; // quick rejected
571 
572     if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
573         bakedState->setupOpacity(op.paint);
574     }
575 
576     currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
577     return bakedState;
578 }
579 
580 /**
581  * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will
582  * be used, since they trigger significantly different rendering paths.
583  *
584  * Note: not used for lines/points, since they don't currently support path effects.
585  */
tessBatchId(const RecordedOp & op)586 static batchid_t tessBatchId(const RecordedOp& op) {
587     const SkPaint& paint = *(op.paint);
588     return paint.getPathEffect()
589             ? OpBatchType::AlphaMaskTexture
590             : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
591 }
592 
deferArcOp(const ArcOp & op)593 void FrameBuilder::deferArcOp(const ArcOp& op) {
594     // Pass true below since arcs have a tendency to draw outside their expected bounds within
595     // their path textures. Passing true makes it more likely that we'll scissor, instead of
596     // corrupting the frame by drawing outside of clip bounds.
597     deferStrokeableOp(op, tessBatchId(op), BakedOpState::StrokeBehavior::StyleDefined, true);
598 }
599 
hasMergeableClip(const BakedOpState & state)600 static bool hasMergeableClip(const BakedOpState& state) {
601     return !state.computedState.clipState
602             || state.computedState.clipState->mode == ClipMode::Rectangle;
603 }
604 
deferBitmapOp(const BitmapOp & op)605 void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
606     BakedOpState* bakedState = tryBakeOpState(op);
607     if (!bakedState) return; // quick rejected
608 
609     if (op.bitmap->isOpaque()) {
610         bakedState->setupOpacity(op.paint);
611     }
612 
613     // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
614     // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
615     // MergingDrawBatch::canMergeWith()
616     if (bakedState->computedState.transform.isSimple()
617             && bakedState->computedState.transform.positiveScale()
618             && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
619             && op.bitmap->colorType() != kAlpha_8_SkColorType
620             && hasMergeableClip(*bakedState)) {
621         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
622         currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
623     } else {
624         currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
625     }
626 }
627 
deferBitmapMeshOp(const BitmapMeshOp & op)628 void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) {
629     BakedOpState* bakedState = tryBakeOpState(op);
630     if (!bakedState) return; // quick rejected
631     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
632 }
633 
deferBitmapRectOp(const BitmapRectOp & op)634 void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) {
635     BakedOpState* bakedState = tryBakeOpState(op);
636     if (!bakedState) return; // quick rejected
637     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
638 }
639 
deferVectorDrawableOp(const VectorDrawableOp & op)640 void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
641     Bitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
642     SkPaint* paint = op.vectorDrawable->getPaint();
643     const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds,
644             op.localMatrix,
645             op.localClip,
646             paint,
647             &bitmap,
648             Rect(bitmap.width(), bitmap.height()));
649     deferBitmapRectOp(*resolvedOp);
650 }
651 
deferCirclePropsOp(const CirclePropsOp & op)652 void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) {
653     // allocate a temporary oval op (with mAllocator, so it persists until render), so the
654     // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
655     float x = *(op.x);
656     float y = *(op.y);
657     float radius = *(op.radius);
658     Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius);
659     const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>(
660             unmappedBounds,
661             op.localMatrix,
662             op.localClip,
663             op.paint);
664     deferOvalOp(*resolvedOp);
665 }
666 
deferColorOp(const ColorOp & op)667 void FrameBuilder::deferColorOp(const ColorOp& op) {
668     BakedOpState* bakedState = tryBakeUnboundedOpState(op);
669     if (!bakedState) return; // quick rejected
670     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
671 }
672 
deferFunctorOp(const FunctorOp & op)673 void FrameBuilder::deferFunctorOp(const FunctorOp& op) {
674     BakedOpState* bakedState = tryBakeUnboundedOpState(op);
675     if (!bakedState) return; // quick rejected
676     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor);
677 }
678 
deferLinesOp(const LinesOp & op)679 void FrameBuilder::deferLinesOp(const LinesOp& op) {
680     batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
681     deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
682 }
683 
deferOvalOp(const OvalOp & op)684 void FrameBuilder::deferOvalOp(const OvalOp& op) {
685     deferStrokeableOp(op, tessBatchId(op));
686 }
687 
deferPatchOp(const PatchOp & op)688 void FrameBuilder::deferPatchOp(const PatchOp& op) {
689     BakedOpState* bakedState = tryBakeOpState(op);
690     if (!bakedState) return; // quick rejected
691 
692     if (bakedState->computedState.transform.isPureTranslate()
693             && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
694             && hasMergeableClip(*bakedState)) {
695         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
696 
697         // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
698         currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
699     } else {
700         // Use Bitmap batchId since Bitmap+Patch use same shader
701         currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
702     }
703 }
704 
deferPathOp(const PathOp & op)705 void FrameBuilder::deferPathOp(const PathOp& op) {
706     auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture);
707     if (CC_LIKELY(state)) {
708         mCaches.pathCache.precache(op.path, op.paint);
709     }
710 }
711 
deferPointsOp(const PointsOp & op)712 void FrameBuilder::deferPointsOp(const PointsOp& op) {
713     batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
714     deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
715 }
716 
deferRectOp(const RectOp & op)717 void FrameBuilder::deferRectOp(const RectOp& op) {
718     deferStrokeableOp(op, tessBatchId(op));
719 }
720 
deferRoundRectOp(const RoundRectOp & op)721 void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) {
722     auto state = deferStrokeableOp(op, tessBatchId(op));
723     if (CC_LIKELY(state && !op.paint->getPathEffect())) {
724         // TODO: consider storing tessellation task in BakedOpState
725         mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint),
726                 op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry);
727     }
728 }
729 
deferRoundRectPropsOp(const RoundRectPropsOp & op)730 void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
731     // allocate a temporary round rect op (with mAllocator, so it persists until render), so the
732     // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
733     const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>(
734             Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
735             op.localMatrix,
736             op.localClip,
737             op.paint, *op.rx, *op.ry);
738     deferRoundRectOp(*resolvedOp);
739 }
740 
deferSimpleRectsOp(const SimpleRectsOp & op)741 void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) {
742     BakedOpState* bakedState = tryBakeOpState(op);
743     if (!bakedState) return; // quick rejected
744     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
745 }
746 
textBatchId(const SkPaint & paint)747 static batchid_t textBatchId(const SkPaint& paint) {
748     // TODO: better handling of shader (since we won't care about color then)
749     return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
750 }
751 
deferTextOp(const TextOp & op)752 void FrameBuilder::deferTextOp(const TextOp& op) {
753     BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
754             mAllocator, *mCanvasState.writableSnapshot(), op,
755             BakedOpState::StrokeBehavior::StyleDefined, false);
756     if (!bakedState) return; // quick rejected
757 
758     batchid_t batchId = textBatchId(*(op.paint));
759     if (bakedState->computedState.transform.isPureTranslate()
760             && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
761             && hasMergeableClip(*bakedState)) {
762         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
763         currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
764     } else {
765         currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
766     }
767 
768     FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
769     auto& totalTransform = bakedState->computedState.transform;
770     if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) {
771         fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
772     } else {
773         // Partial transform case, see BakedOpDispatcher::renderTextOp
774         float sx, sy;
775         totalTransform.decomposeScale(sx, sy);
776         fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale(
777                 roundf(std::max(1.0f, sx)),
778                 roundf(std::max(1.0f, sy))));
779     }
780 }
781 
deferTextOnPathOp(const TextOnPathOp & op)782 void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
783     BakedOpState* bakedState = tryBakeUnboundedOpState(op);
784     if (!bakedState) return; // quick rejected
785     currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
786 
787     mCaches.fontRenderer.getFontRenderer().precache(
788             op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
789 }
790 
deferTextureLayerOp(const TextureLayerOp & op)791 void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) {
792     GlLayer* layer = static_cast<GlLayer*>(op.layerHandle->backingLayer());
793     if (CC_UNLIKELY(!layer || !layer->isRenderable())) return;
794 
795     const TextureLayerOp* textureLayerOp = &op;
796     // Now safe to access transform (which was potentially unready at record time)
797     if (!layer->getTransform().isIdentity()) {
798         // non-identity transform present, so 'inject it' into op by copying + replacing matrix
799         Matrix4 combinedMatrix(op.localMatrix);
800         combinedMatrix.multiply(layer->getTransform());
801         textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix);
802     }
803     BakedOpState* bakedState = tryBakeOpState(*textureLayerOp);
804 
805     if (!bakedState) return; // quick rejected
806     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer);
807 }
808 
saveForLayer(uint32_t layerWidth,uint32_t layerHeight,float contentTranslateX,float contentTranslateY,const Rect & repaintRect,const Vector3 & lightCenter,const BeginLayerOp * beginLayerOp,RenderNode * renderNode)809 void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
810         float contentTranslateX, float contentTranslateY,
811         const Rect& repaintRect,
812         const Vector3& lightCenter,
813         const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
814     mCanvasState.save(SaveFlags::MatrixClip);
815     mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
816     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
817     mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
818     mCanvasState.writableSnapshot()->transform->loadTranslate(
819             contentTranslateX, contentTranslateY, 0);
820     mCanvasState.writableSnapshot()->setClip(
821             repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
822 
823     // create a new layer repaint, and push its index on the stack
824     mLayerStack.push_back(mLayerBuilders.size());
825     auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight,
826             repaintRect, beginLayerOp, renderNode);
827     mLayerBuilders.push_back(newFbo);
828 }
829 
restoreForLayer()830 void FrameBuilder::restoreForLayer() {
831     // restore canvas, and pop finished layer off of the stack
832     mCanvasState.restore();
833     mLayerStack.pop_back();
834 }
835 
836 // TODO: defer time rejection (when bounds become empty) + tests
837 // Option - just skip layers with no bounds at playback + defer?
deferBeginLayerOp(const BeginLayerOp & op)838 void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) {
839     uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
840     uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
841 
842     auto previous = mCanvasState.currentSnapshot();
843     Vector3 lightCenter = previous->getRelativeLightCenter();
844 
845     // Combine all transforms used to present saveLayer content:
846     // parent content transform * canvas transform * bounds offset
847     Matrix4 contentTransform(*(previous->transform));
848     contentTransform.multiply(op.localMatrix);
849     contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
850 
851     Matrix4 inverseContentTransform;
852     inverseContentTransform.loadInverse(contentTransform);
853 
854     // map the light center into layer-relative space
855     inverseContentTransform.mapPoint3d(lightCenter);
856 
857     // Clip bounds of temporary layer to parent's clip rect, so:
858     Rect saveLayerBounds(layerWidth, layerHeight);
859     //     1) transform Rect(width, height) into parent's space
860     //        note: left/top offsets put in contentTransform above
861     contentTransform.mapRect(saveLayerBounds);
862     //     2) intersect with parent's clip
863     saveLayerBounds.doIntersect(previous->getRenderTargetClip());
864     //     3) and transform back
865     inverseContentTransform.mapRect(saveLayerBounds);
866     saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
867     saveLayerBounds.roundOut();
868 
869     // if bounds are reduced, will clip the layer's area by reducing required bounds...
870     layerWidth = saveLayerBounds.getWidth();
871     layerHeight = saveLayerBounds.getHeight();
872     // ...and shifting drawing content to account for left/top side clipping
873     float contentTranslateX = -saveLayerBounds.left;
874     float contentTranslateY = -saveLayerBounds.top;
875 
876     saveForLayer(layerWidth, layerHeight,
877             contentTranslateX, contentTranslateY,
878             Rect(layerWidth, layerHeight),
879             lightCenter,
880             &op, nullptr);
881 }
882 
deferEndLayerOp(const EndLayerOp &)883 void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) {
884     const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
885     int finishedLayerIndex = mLayerStack.back();
886 
887     restoreForLayer();
888 
889     // saveLayer will clip & translate the draw contents, so we need
890     // to translate the drawLayer by how much the contents was translated
891     // TODO: Unify this with beginLayerOp so we don't have to calculate this
892     // twice
893     uint32_t layerWidth = (uint32_t) beginLayerOp.unmappedBounds.getWidth();
894     uint32_t layerHeight = (uint32_t) beginLayerOp.unmappedBounds.getHeight();
895 
896     auto previous = mCanvasState.currentSnapshot();
897     Vector3 lightCenter = previous->getRelativeLightCenter();
898 
899     // Combine all transforms used to present saveLayer content:
900     // parent content transform * canvas transform * bounds offset
901     Matrix4 contentTransform(*(previous->transform));
902     contentTransform.multiply(beginLayerOp.localMatrix);
903     contentTransform.translate(beginLayerOp.unmappedBounds.left,
904             beginLayerOp.unmappedBounds.top);
905 
906     Matrix4 inverseContentTransform;
907     inverseContentTransform.loadInverse(contentTransform);
908 
909     // map the light center into layer-relative space
910     inverseContentTransform.mapPoint3d(lightCenter);
911 
912     // Clip bounds of temporary layer to parent's clip rect, so:
913     Rect saveLayerBounds(layerWidth, layerHeight);
914     //     1) transform Rect(width, height) into parent's space
915     //        note: left/top offsets put in contentTransform above
916     contentTransform.mapRect(saveLayerBounds);
917     //     2) intersect with parent's clip
918     saveLayerBounds.doIntersect(previous->getRenderTargetClip());
919     //     3) and transform back
920     inverseContentTransform.mapRect(saveLayerBounds);
921     saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
922     saveLayerBounds.roundOut();
923 
924     Matrix4 localMatrix(beginLayerOp.localMatrix);
925     localMatrix.translate(saveLayerBounds.left, saveLayerBounds.top);
926 
927     // record the draw operation into the previous layer's list of draw commands
928     // uses state from the associated beginLayerOp, since it has all the state needed for drawing
929     LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(
930             beginLayerOp.unmappedBounds,
931             localMatrix,
932             beginLayerOp.localClip,
933             beginLayerOp.paint,
934             &(mLayerBuilders[finishedLayerIndex]->offscreenBuffer));
935     BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
936 
937     if (bakedOpState) {
938         // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
939         currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
940     } else {
941         // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
942         // TODO: need to prevent any render work from being done
943         // - create layerop earlier for reject purposes?
944         mLayerBuilders[finishedLayerIndex]->clear();
945         return;
946     }
947 }
948 
deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp & op)949 void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
950     Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
951     boundsTransform.multiply(op.localMatrix);
952 
953     Rect dstRect(op.unmappedBounds);
954     boundsTransform.mapRect(dstRect);
955     dstRect.roundOut();
956     dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip());
957 
958     if (dstRect.isEmpty()) {
959         // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored
960         currentLayer().activeUnclippedSaveLayers.push_back(nullptr);
961     } else {
962         // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume)
963         OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr);
964 
965         /**
966          * First, defer an operation to copy out the content from the rendertarget into a layer.
967          */
968         auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle);
969         BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator,
970                 &(currentLayer().repaintClip), dstRect, *copyToOp);
971         currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
972 
973         /**
974          * Defer a clear rect, so that clears from multiple unclipped layers can be drawn
975          * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible
976          */
977         currentLayer().deferLayerClear(dstRect);
978 
979         /**
980          * And stash an operation to copy that layer back under the rendertarget until
981          * a balanced EndUnclippedLayerOp is seen
982          */
983         auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle);
984         bakedState = BakedOpState::directConstruct(mAllocator,
985                 &(currentLayer().repaintClip), dstRect, *copyFromOp);
986         currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
987     }
988 }
989 
deferEndUnclippedLayerOp(const EndUnclippedLayerOp &)990 void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) {
991     LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
992 
993     BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
994     currentLayer().activeUnclippedSaveLayers.pop_back();
995     if (copyFromLayerOp) {
996         currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer);
997     }
998 }
999 
finishDefer()1000 void FrameBuilder::finishDefer() {
1001     mCaches.fontRenderer.endPrecaching();
1002 }
1003 
1004 } // namespace uirenderer
1005 } // namespace android
1006