FrameBuilder.cpp revision fc29f7acd1352efa97269b5f3856eb879d5cfd53
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
32namespace android {
33namespace uirenderer {
34
35FrameBuilder::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
55FrameBuilder::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
77void 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
110void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
111    renderNode.computeOrdering();
112
113    mCanvasState.save(SaveFlags::MatrixClip);
114    deferNodePropsAndOps(renderNode);
115    mCanvasState.restore();
116}
117
118void 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
129static Rect nodeBounds(RenderNode& node) {
130    auto& props = node.properties();
131    return Rect(props.getLeft(), props.getTop(),
132            props.getRight(), props.getBottom());
133}
134
135void 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
206void FrameBuilder::onViewportInitialized() {}
207
208void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
209
210void 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
311typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
312
313template <typename V>
314static 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
336template <typename V>
337static 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
344template <typename V>
345void 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
396void 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
470void 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)); },
514void 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
539void 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
554void 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 */
564BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
565        BakedOpState::StrokeBehavior strokeBehavior) {
566    // Note: here we account for stroke when baking the op
567    BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
568            mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
569    if (!bakedState) return nullptr; // quick rejected
570
571    if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
572        bakedState->setupOpacity(op.paint);
573    }
574
575    currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
576    return bakedState;
577}
578
579/**
580 * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will
581 * be used, since they trigger significantly different rendering paths.
582 *
583 * Note: not used for lines/points, since they don't currently support path effects.
584 */
585static batchid_t tessBatchId(const RecordedOp& op) {
586    const SkPaint& paint = *(op.paint);
587    return paint.getPathEffect()
588            ? OpBatchType::AlphaMaskTexture
589            : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
590}
591
592void FrameBuilder::deferArcOp(const ArcOp& op) {
593    deferStrokeableOp(op, tessBatchId(op));
594}
595
596static bool hasMergeableClip(const BakedOpState& state) {
597    return !state.computedState.clipState
598            || state.computedState.clipState->mode == ClipMode::Rectangle;
599}
600
601void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
602    BakedOpState* bakedState = tryBakeOpState(op);
603    if (!bakedState) return; // quick rejected
604
605    if (op.bitmap->isOpaque()) {
606        bakedState->setupOpacity(op.paint);
607    }
608
609    // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
610    // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
611    // MergingDrawBatch::canMergeWith()
612    if (bakedState->computedState.transform.isSimple()
613            && bakedState->computedState.transform.positiveScale()
614            && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
615            && op.bitmap->colorType() != kAlpha_8_SkColorType
616            && hasMergeableClip(*bakedState)) {
617        mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
618        currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
619    } else {
620        currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
621    }
622}
623
624void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) {
625    BakedOpState* bakedState = tryBakeOpState(op);
626    if (!bakedState) return; // quick rejected
627    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
628}
629
630void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) {
631    BakedOpState* bakedState = tryBakeOpState(op);
632    if (!bakedState) return; // quick rejected
633    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
634}
635
636void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
637    Bitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
638    SkPaint* paint = op.vectorDrawable->getPaint();
639    const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds,
640            op.localMatrix,
641            op.localClip,
642            paint,
643            &bitmap,
644            Rect(bitmap.width(), bitmap.height()));
645    deferBitmapRectOp(*resolvedOp);
646}
647
648void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) {
649    // allocate a temporary oval op (with mAllocator, so it persists until render), so the
650    // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
651    float x = *(op.x);
652    float y = *(op.y);
653    float radius = *(op.radius);
654    Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius);
655    const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>(
656            unmappedBounds,
657            op.localMatrix,
658            op.localClip,
659            op.paint);
660    deferOvalOp(*resolvedOp);
661}
662
663void FrameBuilder::deferColorOp(const ColorOp& op) {
664    BakedOpState* bakedState = tryBakeUnboundedOpState(op);
665    if (!bakedState) return; // quick rejected
666    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
667}
668
669void FrameBuilder::deferFunctorOp(const FunctorOp& op) {
670    BakedOpState* bakedState = tryBakeUnboundedOpState(op);
671    if (!bakedState) return; // quick rejected
672    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor);
673}
674
675void FrameBuilder::deferLinesOp(const LinesOp& op) {
676    batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
677    deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
678}
679
680void FrameBuilder::deferOvalOp(const OvalOp& op) {
681    deferStrokeableOp(op, tessBatchId(op));
682}
683
684void FrameBuilder::deferPatchOp(const PatchOp& op) {
685    BakedOpState* bakedState = tryBakeOpState(op);
686    if (!bakedState) return; // quick rejected
687
688    if (bakedState->computedState.transform.isPureTranslate()
689            && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
690            && hasMergeableClip(*bakedState)) {
691        mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
692
693        // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
694        currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
695    } else {
696        // Use Bitmap batchId since Bitmap+Patch use same shader
697        currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
698    }
699}
700
701void FrameBuilder::deferPathOp(const PathOp& op) {
702    auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture);
703    if (CC_LIKELY(state)) {
704        mCaches.pathCache.precache(op.path, op.paint);
705    }
706}
707
708void FrameBuilder::deferPointsOp(const PointsOp& op) {
709    batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
710    deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
711}
712
713void FrameBuilder::deferRectOp(const RectOp& op) {
714    deferStrokeableOp(op, tessBatchId(op));
715}
716
717void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) {
718    auto state = deferStrokeableOp(op, tessBatchId(op));
719    if (CC_LIKELY(state && !op.paint->getPathEffect())) {
720        // TODO: consider storing tessellation task in BakedOpState
721        mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint),
722                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry);
723    }
724}
725
726void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
727    // allocate a temporary round rect op (with mAllocator, so it persists until render), so the
728    // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
729    const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>(
730            Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
731            op.localMatrix,
732            op.localClip,
733            op.paint, *op.rx, *op.ry);
734    deferRoundRectOp(*resolvedOp);
735}
736
737void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) {
738    BakedOpState* bakedState = tryBakeOpState(op);
739    if (!bakedState) return; // quick rejected
740    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
741}
742
743static batchid_t textBatchId(const SkPaint& paint) {
744    // TODO: better handling of shader (since we won't care about color then)
745    return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
746}
747
748void FrameBuilder::deferTextOp(const TextOp& op) {
749    BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
750            mAllocator, *mCanvasState.writableSnapshot(), op,
751            BakedOpState::StrokeBehavior::StyleDefined);
752    if (!bakedState) return; // quick rejected
753
754    batchid_t batchId = textBatchId(*(op.paint));
755    if (bakedState->computedState.transform.isPureTranslate()
756            && PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver
757            && hasMergeableClip(*bakedState)) {
758        mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
759        currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
760    } else {
761        currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
762    }
763
764    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
765    auto& totalTransform = bakedState->computedState.transform;
766    if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) {
767        fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
768    } else {
769        // Partial transform case, see BakedOpDispatcher::renderTextOp
770        float sx, sy;
771        totalTransform.decomposeScale(sx, sy);
772        fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale(
773                roundf(std::max(1.0f, sx)),
774                roundf(std::max(1.0f, sy))));
775    }
776}
777
778void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
779    BakedOpState* bakedState = tryBakeUnboundedOpState(op);
780    if (!bakedState) return; // quick rejected
781    currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
782
783    mCaches.fontRenderer.getFontRenderer().precache(
784            op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
785}
786
787void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) {
788    GlLayer* layer = static_cast<GlLayer*>(op.layerHandle->backingLayer());
789    if (CC_UNLIKELY(!layer || !layer->isRenderable())) return;
790
791    const TextureLayerOp* textureLayerOp = &op;
792    // Now safe to access transform (which was potentially unready at record time)
793    if (!layer->getTransform().isIdentity()) {
794        // non-identity transform present, so 'inject it' into op by copying + replacing matrix
795        Matrix4 combinedMatrix(op.localMatrix);
796        combinedMatrix.multiply(layer->getTransform());
797        textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix);
798    }
799    BakedOpState* bakedState = tryBakeOpState(*textureLayerOp);
800
801    if (!bakedState) return; // quick rejected
802    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer);
803}
804
805void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
806        float contentTranslateX, float contentTranslateY,
807        const Rect& repaintRect,
808        const Vector3& lightCenter,
809        const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
810    mCanvasState.save(SaveFlags::MatrixClip);
811    mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
812    mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
813    mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
814    mCanvasState.writableSnapshot()->transform->loadTranslate(
815            contentTranslateX, contentTranslateY, 0);
816    mCanvasState.writableSnapshot()->setClip(
817            repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
818
819    // create a new layer repaint, and push its index on the stack
820    mLayerStack.push_back(mLayerBuilders.size());
821    auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight,
822            repaintRect, beginLayerOp, renderNode);
823    mLayerBuilders.push_back(newFbo);
824}
825
826void FrameBuilder::restoreForLayer() {
827    // restore canvas, and pop finished layer off of the stack
828    mCanvasState.restore();
829    mLayerStack.pop_back();
830}
831
832// TODO: defer time rejection (when bounds become empty) + tests
833// Option - just skip layers with no bounds at playback + defer?
834void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) {
835    uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
836    uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
837
838    auto previous = mCanvasState.currentSnapshot();
839    Vector3 lightCenter = previous->getRelativeLightCenter();
840
841    // Combine all transforms used to present saveLayer content:
842    // parent content transform * canvas transform * bounds offset
843    Matrix4 contentTransform(*(previous->transform));
844    contentTransform.multiply(op.localMatrix);
845    contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
846
847    Matrix4 inverseContentTransform;
848    inverseContentTransform.loadInverse(contentTransform);
849
850    // map the light center into layer-relative space
851    inverseContentTransform.mapPoint3d(lightCenter);
852
853    // Clip bounds of temporary layer to parent's clip rect, so:
854    Rect saveLayerBounds(layerWidth, layerHeight);
855    //     1) transform Rect(width, height) into parent's space
856    //        note: left/top offsets put in contentTransform above
857    contentTransform.mapRect(saveLayerBounds);
858    //     2) intersect with parent's clip
859    saveLayerBounds.doIntersect(previous->getRenderTargetClip());
860    //     3) and transform back
861    inverseContentTransform.mapRect(saveLayerBounds);
862    saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
863    saveLayerBounds.roundOut();
864
865    // if bounds are reduced, will clip the layer's area by reducing required bounds...
866    layerWidth = saveLayerBounds.getWidth();
867    layerHeight = saveLayerBounds.getHeight();
868    // ...and shifting drawing content to account for left/top side clipping
869    float contentTranslateX = -saveLayerBounds.left;
870    float contentTranslateY = -saveLayerBounds.top;
871
872    saveForLayer(layerWidth, layerHeight,
873            contentTranslateX, contentTranslateY,
874            Rect(layerWidth, layerHeight),
875            lightCenter,
876            &op, nullptr);
877}
878
879void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) {
880    const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
881    int finishedLayerIndex = mLayerStack.back();
882
883    restoreForLayer();
884
885    // saveLayer will clip & translate the draw contents, so we need
886    // to translate the drawLayer by how much the contents was translated
887    // TODO: Unify this with beginLayerOp so we don't have to calculate this
888    // twice
889    uint32_t layerWidth = (uint32_t) beginLayerOp.unmappedBounds.getWidth();
890    uint32_t layerHeight = (uint32_t) beginLayerOp.unmappedBounds.getHeight();
891
892    auto previous = mCanvasState.currentSnapshot();
893    Vector3 lightCenter = previous->getRelativeLightCenter();
894
895    // Combine all transforms used to present saveLayer content:
896    // parent content transform * canvas transform * bounds offset
897    Matrix4 contentTransform(*(previous->transform));
898    contentTransform.multiply(beginLayerOp.localMatrix);
899    contentTransform.translate(beginLayerOp.unmappedBounds.left,
900            beginLayerOp.unmappedBounds.top);
901
902    Matrix4 inverseContentTransform;
903    inverseContentTransform.loadInverse(contentTransform);
904
905    // map the light center into layer-relative space
906    inverseContentTransform.mapPoint3d(lightCenter);
907
908    // Clip bounds of temporary layer to parent's clip rect, so:
909    Rect saveLayerBounds(layerWidth, layerHeight);
910    //     1) transform Rect(width, height) into parent's space
911    //        note: left/top offsets put in contentTransform above
912    contentTransform.mapRect(saveLayerBounds);
913    //     2) intersect with parent's clip
914    saveLayerBounds.doIntersect(previous->getRenderTargetClip());
915    //     3) and transform back
916    inverseContentTransform.mapRect(saveLayerBounds);
917    saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
918    saveLayerBounds.roundOut();
919
920    Matrix4 localMatrix(beginLayerOp.localMatrix);
921    localMatrix.translate(saveLayerBounds.left, saveLayerBounds.top);
922
923    // record the draw operation into the previous layer's list of draw commands
924    // uses state from the associated beginLayerOp, since it has all the state needed for drawing
925    LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(
926            beginLayerOp.unmappedBounds,
927            localMatrix,
928            beginLayerOp.localClip,
929            beginLayerOp.paint,
930            &(mLayerBuilders[finishedLayerIndex]->offscreenBuffer));
931    BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
932
933    if (bakedOpState) {
934        // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
935        currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
936    } else {
937        // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
938        // TODO: need to prevent any render work from being done
939        // - create layerop earlier for reject purposes?
940        mLayerBuilders[finishedLayerIndex]->clear();
941        return;
942    }
943}
944
945void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
946    Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
947    boundsTransform.multiply(op.localMatrix);
948
949    Rect dstRect(op.unmappedBounds);
950    boundsTransform.mapRect(dstRect);
951    dstRect.roundOut();
952    dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip());
953
954    if (dstRect.isEmpty()) {
955        // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored
956        currentLayer().activeUnclippedSaveLayers.push_back(nullptr);
957    } else {
958        // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume)
959        OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr);
960
961        /**
962         * First, defer an operation to copy out the content from the rendertarget into a layer.
963         */
964        auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle);
965        BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator,
966                &(currentLayer().repaintClip), dstRect, *copyToOp);
967        currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
968
969        /**
970         * Defer a clear rect, so that clears from multiple unclipped layers can be drawn
971         * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible
972         */
973        currentLayer().deferLayerClear(dstRect);
974
975        /**
976         * And stash an operation to copy that layer back under the rendertarget until
977         * a balanced EndUnclippedLayerOp is seen
978         */
979        auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle);
980        bakedState = BakedOpState::directConstruct(mAllocator,
981                &(currentLayer().repaintClip), dstRect, *copyFromOp);
982        currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
983    }
984}
985
986void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) {
987    LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
988
989    BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
990    currentLayer().activeUnclippedSaveLayers.pop_back();
991    if (copyFromLayerOp) {
992        currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer);
993    }
994}
995
996void FrameBuilder::finishDefer() {
997    mCaches.fontRenderer.endPrecaching();
998}
999
1000} // namespace uirenderer
1001} // namespace android
1002