RenderNode.cpp revision b265e2ca50b6ceb2fd2987ef1f7d063b1bde19ae
1/*
2 * Copyright (C) 2014 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#define ATRACE_TAG ATRACE_TAG_VIEW
18
19#include "RenderNode.h"
20
21#include <SkCanvas.h>
22#include <algorithm>
23
24#include <utils/Trace.h>
25
26#include "Debug.h"
27#include "DisplayListOp.h"
28#include "DisplayListLogBuffer.h"
29
30namespace android {
31namespace uirenderer {
32
33void RenderNode::outputLogBuffer(int fd) {
34    DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
35    if (logBuffer.isEmpty()) {
36        return;
37    }
38
39    FILE *file = fdopen(fd, "a");
40
41    fprintf(file, "\nRecent DisplayList operations\n");
42    logBuffer.outputCommands(file);
43
44    String8 cachesLog;
45    Caches::getInstance().dumpMemoryUsage(cachesLog);
46    fprintf(file, "\nCaches:\n%s", cachesLog.string());
47    fprintf(file, "\n");
48
49    fflush(file);
50}
51
52RenderNode::RenderNode() : mDestroyed(false), mNeedsPropertiesSync(false), mDisplayListData(0) {
53}
54
55RenderNode::~RenderNode() {
56    LOG_ALWAYS_FATAL_IF(mDestroyed, "Double destroyed DisplayList %p", this);
57
58    mDestroyed = true;
59    delete mDisplayListData;
60}
61
62void RenderNode::destroyDisplayListDeferred(RenderNode* displayList) {
63    if (displayList) {
64        DISPLAY_LIST_LOGD("Deferring display list destruction");
65        Caches::getInstance().deleteDisplayListDeferred(displayList);
66    }
67}
68
69void RenderNode::setData(DisplayListData* data) {
70    delete mDisplayListData;
71    mDisplayListData = data;
72    if (mDisplayListData) {
73        Caches::getInstance().registerFunctors(mDisplayListData->functorCount);
74    }
75}
76
77/**
78 * This function is a simplified version of replay(), where we simply retrieve and log the
79 * display list. This function should remain in sync with the replay() function.
80 */
81void RenderNode::output(uint32_t level) {
82    ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this,
83            mName.string(), isRenderable());
84    ALOGD("%*s%s %d", level * 2, "", "Save",
85            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
86
87    properties().debugOutputProperties(level);
88    int flags = DisplayListOp::kOpLogFlag_Recurse;
89    for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
90        mDisplayListData->displayListOps[i]->output(level, flags);
91    }
92
93    ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, mName.string());
94}
95
96void RenderNode::updateProperties() {
97    if (mNeedsPropertiesSync) {
98        mNeedsPropertiesSync = false;
99        mProperties = mStagingProperties;
100    }
101
102    if (mDisplayListData) {
103        for (size_t i = 0; i < mDisplayListData->children.size(); i++) {
104            RenderNode* childNode = mDisplayListData->children[i]->mDisplayList;
105            childNode->updateProperties();
106        }
107    }
108}
109
110bool RenderNode::hasFunctors() {
111    if (!mDisplayListData) return false;
112
113    if (mDisplayListData->functorCount) {
114        return true;
115    }
116
117    for (size_t i = 0; i < mDisplayListData->children.size(); i++) {
118        RenderNode* childNode = mDisplayListData->children[i]->mDisplayList;
119        if (childNode->hasFunctors()) {
120            return true;
121        }
122    }
123
124    return false;
125}
126
127/*
128 * For property operations, we pass a savecount of 0, since the operations aren't part of the
129 * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
130 * base saveCount (i.e., how RestoreToCount uses saveCount + properties().getCount())
131 */
132#define PROPERTY_SAVECOUNT 0
133
134template <class T>
135void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) {
136#if DEBUG_DISPLAY_LIST
137    properties().debugOutputProperties(handler.level() + 1);
138#endif
139    if (properties().getLeft() != 0 || properties().getTop() != 0) {
140        renderer.translate(properties().getLeft(), properties().getTop());
141    }
142    if (properties().getStaticMatrix()) {
143        renderer.concatMatrix(properties().getStaticMatrix());
144    } else if (properties().getAnimationMatrix()) {
145        renderer.concatMatrix(properties().getAnimationMatrix());
146    }
147    if (properties().getMatrixFlags() != 0) {
148        if (properties().getMatrixFlags() == TRANSLATION) {
149            renderer.translate(properties().getTranslationX(), properties().getTranslationY());
150        } else {
151            renderer.concatMatrix(*properties().getTransformMatrix());
152        }
153    }
154    bool clipToBoundsNeeded = properties().getCaching() ? false : properties().getClipToBounds();
155    if (properties().getAlpha() < 1) {
156        if (properties().getCaching()) {
157            renderer.setOverrideLayerAlpha(properties().getAlpha());
158        } else if (!properties().getHasOverlappingRendering()) {
159            renderer.scaleAlpha(properties().getAlpha());
160        } else {
161            // TODO: should be able to store the size of a DL at record time and not
162            // have to pass it into this call. In fact, this information might be in the
163            // location/size info that we store with the new native transform data.
164            int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
165            if (clipToBoundsNeeded) {
166                saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
167                clipToBoundsNeeded = false; // clipping done by saveLayer
168            }
169
170            SaveLayerOp* op = new (handler.allocator()) SaveLayerOp(
171                    0, 0, properties().getWidth(), properties().getHeight(),
172                    properties().getAlpha() * 255, saveFlags);
173            handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
174        }
175    }
176    if (clipToBoundsNeeded) {
177        ClipRectOp* op = new (handler.allocator()) ClipRectOp(
178                0, 0, properties().getWidth(), properties().getHeight(), SkRegion::kIntersect_Op);
179        handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
180    }
181
182    if (CC_UNLIKELY(properties().hasClippingPath())) {
183        // TODO: optimize for round rect/circle clipping
184        const SkPath* path = properties().getClippingPath();
185        ClipPathOp* op = new (handler.allocator()) ClipPathOp(path, SkRegion::kIntersect_Op);
186        handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
187    }
188}
189
190/**
191 * Apply property-based transformations to input matrix
192 *
193 * If true3dTransform is set to true, the transform applied to the input matrix will use true 4x4
194 * matrix computation instead of the Skia 3x3 matrix + camera hackery.
195 */
196void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) {
197    if (properties().getLeft() != 0 || properties().getTop() != 0) {
198        matrix.translate(properties().getLeft(), properties().getTop());
199    }
200    if (properties().getStaticMatrix()) {
201        mat4 stat(*properties().getStaticMatrix());
202        matrix.multiply(stat);
203    } else if (properties().getAnimationMatrix()) {
204        mat4 anim(*properties().getAnimationMatrix());
205        matrix.multiply(anim);
206    }
207    if (properties().getMatrixFlags() != 0) {
208        if (properties().getMatrixFlags() == TRANSLATION) {
209            matrix.translate(properties().getTranslationX(), properties().getTranslationY(),
210                    true3dTransform ? properties().getTranslationZ() : 0.0f);
211        } else {
212            if (!true3dTransform) {
213                matrix.multiply(*properties().getTransformMatrix());
214            } else {
215                mat4 true3dMat;
216                true3dMat.loadTranslate(
217                        properties().getPivotX() + properties().getTranslationX(),
218                        properties().getPivotY() + properties().getTranslationY(),
219                        properties().getTranslationZ());
220                true3dMat.rotate(properties().getRotationX(), 1, 0, 0);
221                true3dMat.rotate(properties().getRotationY(), 0, 1, 0);
222                true3dMat.rotate(properties().getRotation(), 0, 0, 1);
223                true3dMat.scale(properties().getScaleX(), properties().getScaleY(), 1);
224                true3dMat.translate(-properties().getPivotX(), -properties().getPivotY());
225
226                matrix.multiply(true3dMat);
227            }
228        }
229    }
230}
231
232/**
233 * Organizes the DisplayList hierarchy to prepare for background projection reordering.
234 *
235 * This should be called before a call to defer() or drawDisplayList()
236 *
237 * Each DisplayList that serves as a 3d root builds its list of composited children,
238 * which are flagged to not draw in the standard draw loop.
239 */
240void RenderNode::computeOrdering() {
241    ATRACE_CALL();
242    mProjectedNodes.clear();
243
244    // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that
245    // transform properties are applied correctly to top level children
246    if (mDisplayListData == NULL) return;
247    for (unsigned int i = 0; i < mDisplayListData->children.size(); i++) {
248        DrawDisplayListOp* childOp = mDisplayListData->children[i];
249        childOp->mDisplayList->computeOrderingImpl(childOp,
250                &mProjectedNodes, &mat4::identity());
251    }
252}
253
254void RenderNode::computeOrderingImpl(
255        DrawDisplayListOp* opState,
256        Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
257        const mat4* transformFromProjectionSurface) {
258    mProjectedNodes.clear();
259    if (mDisplayListData == NULL || mDisplayListData->isEmpty()) return;
260
261    // TODO: should avoid this calculation in most cases
262    // TODO: just calculate single matrix, down to all leaf composited elements
263    Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
264    localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
265
266    if (properties().getProjectBackwards()) {
267        // composited projectee, flag for out of order draw, save matrix, and store in proj surface
268        opState->mSkipInOrderDraw = true;
269        opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface);
270        compositedChildrenOfProjectionSurface->add(opState);
271    } else {
272        // standard in order draw
273        opState->mSkipInOrderDraw = false;
274    }
275
276    if (mDisplayListData->children.size() > 0) {
277        const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0;
278        bool haveAppliedPropertiesToProjection = false;
279        for (unsigned int i = 0; i < mDisplayListData->children.size(); i++) {
280            DrawDisplayListOp* childOp = mDisplayListData->children[i];
281            RenderNode* child = childOp->mDisplayList;
282
283            Vector<DrawDisplayListOp*>* projectionChildren = NULL;
284            const mat4* projectionTransform = NULL;
285            if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
286                // if receiving projections, collect projecting descendent
287
288                // Note that if a direct descendent is projecting backwards, we pass it's
289                // grandparent projection collection, since it shouldn't project onto it's
290                // parent, where it will already be drawing.
291                projectionChildren = &mProjectedNodes;
292                projectionTransform = &mat4::identity();
293            } else {
294                if (!haveAppliedPropertiesToProjection) {
295                    applyViewPropertyTransforms(localTransformFromProjectionSurface);
296                    haveAppliedPropertiesToProjection = true;
297                }
298                projectionChildren = compositedChildrenOfProjectionSurface;
299                projectionTransform = &localTransformFromProjectionSurface;
300            }
301            child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
302        }
303    }
304}
305
306class DeferOperationHandler {
307public:
308    DeferOperationHandler(DeferStateStruct& deferStruct, int level)
309        : mDeferStruct(deferStruct), mLevel(level) {}
310    inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
311        operation->defer(mDeferStruct, saveCount, mLevel, clipToBounds);
312    }
313    inline LinearAllocator& allocator() { return *(mDeferStruct.mAllocator); }
314    inline void startMark(const char* name) {} // do nothing
315    inline void endMark() {}
316    inline int level() { return mLevel; }
317    inline int replayFlags() { return mDeferStruct.mReplayFlags; }
318
319private:
320    DeferStateStruct& mDeferStruct;
321    const int mLevel;
322};
323
324void RenderNode::deferNodeTree(DeferStateStruct& deferStruct) {
325    DeferOperationHandler handler(deferStruct, 0);
326    if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
327    issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
328}
329
330void RenderNode::deferNodeInParent(DeferStateStruct& deferStruct, const int level) {
331    DeferOperationHandler handler(deferStruct, level);
332    issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
333}
334
335class ReplayOperationHandler {
336public:
337    ReplayOperationHandler(ReplayStateStruct& replayStruct, int level)
338        : mReplayStruct(replayStruct), mLevel(level) {}
339    inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
340#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
341        properties().getReplayStruct().mRenderer.eventMark(operation->name());
342#endif
343        operation->replay(mReplayStruct, saveCount, mLevel, clipToBounds);
344    }
345    inline LinearAllocator& allocator() { return *(mReplayStruct.mAllocator); }
346    inline void startMark(const char* name) {
347        mReplayStruct.mRenderer.startMark(name);
348    }
349    inline void endMark() {
350        mReplayStruct.mRenderer.endMark();
351        DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", level * 2, "", this, mName.string(),
352                mReplayStruct.mDrawGlStatus);
353    }
354    inline int level() { return mLevel; }
355    inline int replayFlags() { return mReplayStruct.mReplayFlags; }
356
357private:
358    ReplayStateStruct& mReplayStruct;
359    const int mLevel;
360};
361
362void RenderNode::replayNodeTree(ReplayStateStruct& replayStruct) {
363    ReplayOperationHandler handler(replayStruct, 0);
364    if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
365    issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
366}
367
368void RenderNode::replayNodeInParent(ReplayStateStruct& replayStruct, const int level) {
369    ReplayOperationHandler handler(replayStruct, level);
370    issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
371}
372
373void RenderNode::buildZSortedChildList(Vector<ZDrawDisplayListOpPair>& zTranslatedNodes) {
374    if (mDisplayListData == NULL || mDisplayListData->children.size() == 0) return;
375
376    for (unsigned int i = 0; i < mDisplayListData->children.size(); i++) {
377        DrawDisplayListOp* childOp = mDisplayListData->children[i];
378        RenderNode* child = childOp->mDisplayList;
379        float childZ = child->properties().getTranslationZ();
380
381        if (childZ != 0.0f) {
382            zTranslatedNodes.add(ZDrawDisplayListOpPair(childZ, childOp));
383            childOp->mSkipInOrderDraw = true;
384        } else if (!child->properties().getProjectBackwards()) {
385            // regular, in order drawing DisplayList
386            childOp->mSkipInOrderDraw = false;
387        }
388    }
389
390    // Z sort 3d children (stable-ness makes z compare fall back to standard drawing order)
391    std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
392}
393
394template <class T>
395void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler) {
396    if (properties().getAlpha() <= 0.0f) return;
397
398    mat4 shadowMatrixXY(transformFromParent);
399    applyViewPropertyTransforms(shadowMatrixXY);
400
401    // Z matrix needs actual 3d transformation, so mapped z values will be correct
402    mat4 shadowMatrixZ(transformFromParent);
403    applyViewPropertyTransforms(shadowMatrixZ, true);
404
405    const SkPath* outlinePath = properties().getOutline().getPath();
406    const RevealClip& revealClip = properties().getRevealClip();
407    const SkPath* revealClipPath = revealClip.hasConvexClip()
408            ?  revealClip.getPath() : NULL; // only pass the reveal clip's path if it's convex
409
410    /**
411     * The drawing area of the caster is always the same as the its perimeter (which
412     * the shadow system uses) *except* in the inverse clip case. Inform the shadow
413     * system that the caster's drawing area (as opposed to its perimeter) has been
414     * clipped, so that it knows the caster can't be opaque.
415     */
416    bool casterUnclipped = !revealClip.willClip() || revealClip.hasConvexClip();
417
418    DisplayListOp* shadowOp  = new (handler.allocator()) DrawShadowOp(
419            shadowMatrixXY, shadowMatrixZ,
420            properties().getAlpha(), casterUnclipped,
421            properties().getWidth(), properties().getHeight(),
422            outlinePath, revealClipPath);
423    handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
424}
425
426#define SHADOW_DELTA 0.1f
427
428template <class T>
429void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawDisplayListOpPair>& zTranslatedNodes,
430        ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler) {
431    const int size = zTranslatedNodes.size();
432    if (size == 0
433            || (mode == kNegativeZChildren && zTranslatedNodes[0].key > 0.0f)
434            || (mode == kPositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) {
435        // no 3d children to draw
436        return;
437    }
438
439    /**
440     * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
441     * with very similar Z heights to draw together.
442     *
443     * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
444     * underneath both, and neither's shadow is drawn on top of the other.
445     */
446    const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
447    size_t drawIndex, shadowIndex, endIndex;
448    if (mode == kNegativeZChildren) {
449        drawIndex = 0;
450        endIndex = nonNegativeIndex;
451        shadowIndex = endIndex; // draw no shadows
452    } else {
453        drawIndex = nonNegativeIndex;
454        endIndex = size;
455        shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
456    }
457    float lastCasterZ = 0.0f;
458    while (shadowIndex < endIndex || drawIndex < endIndex) {
459        if (shadowIndex < endIndex) {
460            DrawDisplayListOp* casterOp = zTranslatedNodes[shadowIndex].value;
461            RenderNode* caster = casterOp->mDisplayList;
462            const float casterZ = zTranslatedNodes[shadowIndex].key;
463            // attempt to render the shadow if the caster about to be drawn is its caster,
464            // OR if its caster's Z value is similar to the previous potential caster
465            if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
466                caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
467
468                lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
469                shadowIndex++;
470                continue;
471            }
472        }
473
474        // only the actual child DL draw needs to be in save/restore,
475        // since it modifies the renderer's matrix
476        int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
477
478        DrawDisplayListOp* childOp = zTranslatedNodes[drawIndex].value;
479        RenderNode* child = childOp->mDisplayList;
480
481        renderer.concatMatrix(childOp->mTransformFromParent);
482        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
483        handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
484        childOp->mSkipInOrderDraw = true;
485
486        renderer.restoreToCount(restoreTo);
487        drawIndex++;
488    }
489}
490
491template <class T>
492void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler) {
493    for (size_t i = 0; i < mProjectedNodes.size(); i++) {
494        DrawDisplayListOp* childOp = mProjectedNodes[i];
495
496        // matrix save, concat, and restore can be done safely without allocating operations
497        int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
498        renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
499        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
500        handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
501        childOp->mSkipInOrderDraw = true;
502        renderer.restoreToCount(restoreTo);
503    }
504}
505
506/**
507 * This function serves both defer and replay modes, and will organize the displayList's component
508 * operations for a single frame:
509 *
510 * Every 'simple' state operation that affects just the matrix and alpha (or other factors of
511 * DeferredDisplayState) may be issued directly to the renderer, but complex operations (with custom
512 * defer logic) and operations in displayListOps are issued through the 'handler' which handles the
513 * defer vs replay logic, per operation
514 */
515template <class T>
516void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
517    const int level = handler.level();
518    if (CC_UNLIKELY(mDestroyed)) { // temporary debug logging
519        ALOGW("Error: %s is drawing after destruction", mName.string());
520        CRASH();
521    }
522    if (mDisplayListData->isEmpty() || properties().getAlpha() <= 0) {
523        DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string());
524        return;
525    }
526
527    handler.startMark(mName.string());
528
529#if DEBUG_DISPLAY_LIST
530    Rect* clipRect = renderer.getClipRect();
531    DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.0f, %.0f, %.0f",
532            level * 2, "", this, mName.string(), clipRect->left, clipRect->top,
533            clipRect->right, clipRect->bottom);
534#endif
535
536    LinearAllocator& alloc = handler.allocator();
537    int restoreTo = renderer.getSaveCount();
538    handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
539            PROPERTY_SAVECOUNT, properties().getClipToBounds());
540
541    DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
542            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
543
544    setViewProperties<T>(renderer, handler);
545
546    bool quickRejected = properties().getClipToBounds()
547            && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
548    if (!quickRejected) {
549        Vector<ZDrawDisplayListOpPair> zTranslatedNodes;
550        buildZSortedChildList(zTranslatedNodes);
551
552        // for 3d root, draw children with negative z values
553        issueOperationsOf3dChildren(zTranslatedNodes, kNegativeZChildren, renderer, handler);
554
555        DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
556        const int saveCountOffset = renderer.getSaveCount() - 1;
557        const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
558        for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
559            DisplayListOp *op = mDisplayListData->displayListOps[i];
560
561#if DEBUG_DISPLAY_LIST
562            op->output(level + 1);
563#endif
564            logBuffer.writeCommand(level, op->name());
565            handler(op, saveCountOffset, properties().getClipToBounds());
566
567            if (CC_UNLIKELY(i == projectionReceiveIndex && mProjectedNodes.size() > 0)) {
568                issueOperationsOfProjectedChildren(renderer, handler);
569            }
570        }
571
572        // for 3d root, draw children with positive z values
573        issueOperationsOf3dChildren(zTranslatedNodes, kPositiveZChildren, renderer, handler);
574    }
575
576    DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
577    handler(new (alloc) RestoreToCountOp(restoreTo),
578            PROPERTY_SAVECOUNT, properties().getClipToBounds());
579    renderer.setOverrideLayerAlpha(1.0f);
580
581    handler.endMark();
582}
583
584} /* namespace uirenderer */
585} /* namespace android */
586