FrameBuilder.cpp revision 49b403dc9c47ada51c8e5b883347682a868515f8
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, 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 */
586static 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
593void 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
600static bool hasMergeableClip(const BakedOpState& state) {
601    return !state.computedState.clipState
602            || state.computedState.clipState->mode == ClipMode::Rectangle;
603}
604
605void 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
628void 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
634void 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
640void 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
652void 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
667void 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
673void 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
679void 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
684void FrameBuilder::deferOvalOp(const OvalOp& op) {
685    deferStrokeableOp(op, tessBatchId(op));
686}
687
688void 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
705void 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
712void 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
717void FrameBuilder::deferRectOp(const RectOp& op) {
718    deferStrokeableOp(op, tessBatchId(op));
719}
720
721void 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
730void 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
741void 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
747static 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
752void 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
782void 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
791void 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
809void 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
830void 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?
838void 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
883void 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
949void 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
990void 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
1000void FrameBuilder::finishDefer() {
1001    mCaches.fontRenderer.endPrecaching();
1002}
1003
1004} // namespace uirenderer
1005} // namespace android
1006