Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2013 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 package android.view;
     18 
     19 import android.annotation.NonNull;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.HardwareRenderer;
     23 import android.graphics.Picture;
     24 import android.graphics.Point;
     25 import android.graphics.RecordingCanvas;
     26 import android.graphics.Rect;
     27 import android.graphics.RenderNode;
     28 import android.os.SystemProperties;
     29 import android.os.Trace;
     30 import android.view.Surface.OutOfResourcesException;
     31 import android.view.View.AttachInfo;
     32 import android.view.animation.AnimationUtils;
     33 
     34 import com.android.internal.R;
     35 
     36 import java.io.FileDescriptor;
     37 import java.io.PrintWriter;
     38 
     39 /**
     40  * Threaded renderer that proxies the rendering to a render thread. Most calls
     41  * are currently synchronous.
     42  *
     43  * The UI thread can block on the RenderThread, but RenderThread must never
     44  * block on the UI thread.
     45  *
     46  * ThreadedRenderer creates an instance of RenderProxy. RenderProxy in turn creates
     47  * and manages a CanvasContext on the RenderThread. The CanvasContext is fully managed
     48  * by the lifecycle of the RenderProxy.
     49  *
     50  * Note that although currently the EGL context & surfaces are created & managed
     51  * by the render thread, the goal is to move that into a shared structure that can
     52  * be managed by both threads. EGLSurface creation & deletion should ideally be
     53  * done on the UI thread and not the RenderThread to avoid stalling the
     54  * RenderThread with surface buffer allocation.
     55  *
     56  * @hide
     57  */
     58 public final class ThreadedRenderer extends HardwareRenderer {
     59     /**
     60      * System property used to enable or disable threaded rendering profiling.
     61      * The default value of this property is assumed to be false.
     62      *
     63      * When profiling is enabled, the adb shell dumpsys gfxinfo command will
     64      * output extra information about the time taken to execute by the last
     65      * frames.
     66      *
     67      * Possible values:
     68      * "true", to enable profiling
     69      * "visual_bars", to enable profiling and visualize the results on screen
     70      * "false", to disable profiling
     71      *
     72      * @see #PROFILE_PROPERTY_VISUALIZE_BARS
     73      *
     74      * @hide
     75      */
     76     public static final String PROFILE_PROPERTY = "debug.hwui.profile";
     77 
     78     /**
     79      * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
     80      * value, profiling data will be visualized on screen as a bar chart.
     81      *
     82      * @hide
     83      */
     84     public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
     85 
     86     /**
     87      * System property used to specify the number of frames to be used
     88      * when doing threaded rendering profiling.
     89      * The default value of this property is #PROFILE_MAX_FRAMES.
     90      *
     91      * When profiling is enabled, the adb shell dumpsys gfxinfo command will
     92      * output extra information about the time taken to execute by the last
     93      * frames.
     94      *
     95      * Possible values:
     96      * "60", to set the limit of frames to 60
     97      */
     98     static final String PROFILE_MAXFRAMES_PROPERTY = "debug.hwui.profile.maxframes";
     99 
    100     /**
    101      * System property used to debug EGL configuration choice.
    102      *
    103      * Possible values:
    104      * "choice", print the chosen configuration only
    105      * "all", print all possible configurations
    106      */
    107     static final String PRINT_CONFIG_PROPERTY = "debug.hwui.print_config";
    108 
    109     /**
    110      * Turn on to draw dirty regions every other frame.
    111      *
    112      * Possible values:
    113      * "true", to enable dirty regions debugging
    114      * "false", to disable dirty regions debugging
    115      *
    116      * @hide
    117      */
    118     public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions";
    119 
    120     /**
    121      * Turn on to flash hardware layers when they update.
    122      *
    123      * Possible values:
    124      * "true", to enable hardware layers updates debugging
    125      * "false", to disable hardware layers updates debugging
    126      *
    127      * @hide
    128      */
    129     public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY =
    130             "debug.hwui.show_layers_updates";
    131 
    132     /**
    133      * Controls overdraw debugging.
    134      *
    135      * Possible values:
    136      * "false", to disable overdraw debugging
    137      * "show", to show overdraw areas on screen
    138      * "count", to display an overdraw counter
    139      *
    140      * @hide
    141      */
    142     public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw";
    143 
    144     /**
    145      * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
    146      * value, overdraw will be shown on screen by coloring pixels.
    147      *
    148      * @hide
    149      */
    150     public static final String OVERDRAW_PROPERTY_SHOW = "show";
    151 
    152     /**
    153      * Turn on to debug non-rectangular clip operations.
    154      *
    155      * Possible values:
    156      * "hide", to disable this debug mode
    157      * "highlight", highlight drawing commands tested against a non-rectangular clip
    158      * "stencil", renders the clip region on screen when set
    159      *
    160      * @hide
    161      */
    162     public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
    163             "debug.hwui.show_non_rect_clip";
    164 
    165     /**
    166      * Sets the FPS devisor to lower the FPS.
    167      *
    168      * Sets a positive integer as a divisor. 1 (the default value) menas the full FPS, and 2
    169      * means half the full FPS.
    170      *
    171      *
    172      * @hide
    173      */
    174     public static final String DEBUG_FPS_DIVISOR = "debug.hwui.fps_divisor";
    175 
    176     /**
    177      * Forces smart-dark to be always on.
    178      * @hide
    179      */
    180     public static final String DEBUG_FORCE_DARK = "debug.hwui.force_dark";
    181 
    182     public static int EGL_CONTEXT_PRIORITY_HIGH_IMG = 0x3101;
    183     public static int EGL_CONTEXT_PRIORITY_MEDIUM_IMG = 0x3102;
    184     public static int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
    185 
    186     static {
    187         // Try to check OpenGL support early if possible.
    188         isAvailable();
    189     }
    190 
    191     /**
    192      * A process can set this flag to false to prevent the use of threaded
    193      * rendering.
    194      *
    195      * @hide
    196      */
    197     public static boolean sRendererDisabled = false;
    198 
    199     /**
    200      * Further threaded renderer disabling for the system process.
    201      *
    202      * @hide
    203      */
    204     public static boolean sSystemRendererDisabled = false;
    205 
    206     /**
    207      * Invoke this method to disable threaded rendering in the current process.
    208      *
    209      * @hide
    210      */
    211     public static void disable(boolean system) {
    212         sRendererDisabled = true;
    213         if (system) {
    214             sSystemRendererDisabled = true;
    215         }
    216     }
    217 
    218     public static boolean sTrimForeground = false;
    219 
    220     /**
    221      * Controls whether or not the renderer should aggressively trim
    222      * memory. Note that this must not be set for any process that uses
    223      * WebView! This should be only used by system_process or similar
    224      * that do not go into the background.
    225      */
    226     public static void enableForegroundTrimming() {
    227         sTrimForeground = true;
    228     }
    229 
    230     private static Boolean sSupportsOpenGL;
    231 
    232     /**
    233      * Indicates whether threaded rendering is available under any form for
    234      * the view hierarchy.
    235      *
    236      * @return True if the view hierarchy can potentially be defer rendered,
    237      *         false otherwise
    238      */
    239     public static boolean isAvailable() {
    240         if (sSupportsOpenGL != null) {
    241             return sSupportsOpenGL.booleanValue();
    242         }
    243         if (SystemProperties.getInt("ro.kernel.qemu", 0) == 0) {
    244             // Device is not an emulator.
    245             sSupportsOpenGL = true;
    246             return true;
    247         }
    248         int qemu_gles = SystemProperties.getInt("qemu.gles", -1);
    249         if (qemu_gles == -1) {
    250             // In this case, the value of the qemu.gles property is not ready
    251             // because the SurfaceFlinger service may not start at this point.
    252             return false;
    253         }
    254         // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
    255         // enabled, 0 otherwise. On old emulator versions it will be undefined.
    256         sSupportsOpenGL = qemu_gles > 0;
    257         return sSupportsOpenGL.booleanValue();
    258     }
    259 
    260     /**
    261      * Creates a threaded renderer using OpenGL.
    262      *
    263      * @param translucent True if the surface is translucent, false otherwise
    264      *
    265      * @return A threaded renderer backed by OpenGL.
    266      */
    267     public static ThreadedRenderer create(Context context, boolean translucent, String name) {
    268         ThreadedRenderer renderer = null;
    269         if (isAvailable()) {
    270             renderer = new ThreadedRenderer(context, translucent, name);
    271         }
    272         return renderer;
    273     }
    274 
    275     private static final String[] VISUALIZERS = {
    276         PROFILE_PROPERTY_VISUALIZE_BARS,
    277     };
    278 
    279     // Size of the rendered content.
    280     private int mWidth, mHeight;
    281 
    282     // Actual size of the drawing surface.
    283     private int mSurfaceWidth, mSurfaceHeight;
    284 
    285     // Insets between the drawing surface and rendered content. These are
    286     // applied as translation when updating the root render node.
    287     private int mInsetTop, mInsetLeft;
    288 
    289     // Whether the surface has insets. Used to protect opacity.
    290     private boolean mHasInsets;
    291 
    292     // Light properties specified by the theme.
    293     private final float mLightY;
    294     private final float mLightZ;
    295     private final float mLightRadius;
    296 
    297     private boolean mInitialized = false;
    298     private boolean mRootNodeNeedsUpdate;
    299 
    300     private boolean mEnabled;
    301     private boolean mRequested = true;
    302 
    303     private FrameDrawingCallback mNextRtFrameCallback;
    304 
    305     ThreadedRenderer(Context context, boolean translucent, String name) {
    306         super();
    307         setName(name);
    308         setOpaque(!translucent);
    309 
    310         final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
    311         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
    312         mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
    313         mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
    314         float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
    315         float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
    316         a.recycle();
    317         setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha);
    318     }
    319 
    320     @Override
    321     public void destroy() {
    322         mInitialized = false;
    323         updateEnabledState(null);
    324         super.destroy();
    325     }
    326 
    327     /**
    328      * Indicates whether threaded rendering is currently enabled.
    329      *
    330      * @return True if threaded rendering  is in use, false otherwise.
    331      */
    332     boolean isEnabled() {
    333         return mEnabled;
    334     }
    335 
    336     /**
    337      * Indicates whether threaded rendering  is currently enabled.
    338      *
    339      * @param enabled True if the threaded renderer is in use, false otherwise.
    340      */
    341     void setEnabled(boolean enabled) {
    342         mEnabled = enabled;
    343     }
    344 
    345     /**
    346      * Indicates whether threaded rendering is currently request but not
    347      * necessarily enabled yet.
    348      *
    349      * @return True if requested, false otherwise.
    350      */
    351     boolean isRequested() {
    352         return mRequested;
    353     }
    354 
    355     /**
    356      * Indicates whether threaded rendering is currently requested but not
    357      * necessarily enabled yet.
    358      */
    359     void setRequested(boolean requested) {
    360         mRequested = requested;
    361     }
    362 
    363     private void updateEnabledState(Surface surface) {
    364         if (surface == null || !surface.isValid()) {
    365             setEnabled(false);
    366         } else {
    367             setEnabled(mInitialized);
    368         }
    369     }
    370 
    371     /**
    372      * Initializes the threaded renderer for the specified surface.
    373      *
    374      * @param surface The surface to render
    375      *
    376      * @return True if the initialization was successful, false otherwise.
    377      */
    378     boolean initialize(Surface surface) throws OutOfResourcesException {
    379         boolean status = !mInitialized;
    380         mInitialized = true;
    381         updateEnabledState(surface);
    382         setSurface(surface);
    383         return status;
    384     }
    385 
    386     /**
    387      * Initializes the threaded renderer for the specified surface and setup the
    388      * renderer for drawing, if needed. This is invoked when the ViewAncestor has
    389      * potentially lost the threaded renderer. The threaded renderer should be
    390      * reinitialized and setup when the render {@link #isRequested()} and
    391      * {@link #isEnabled()}.
    392      *
    393      * @param width The width of the drawing surface.
    394      * @param height The height of the drawing surface.
    395      * @param attachInfo Information about the window.
    396      * @param surface The surface to render
    397      * @param surfaceInsets The drawing surface insets to apply
    398      *
    399      * @return true if the surface was initialized, false otherwise. Returning
    400      *         false might mean that the surface was already initialized.
    401      */
    402     boolean initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
    403             Surface surface, Rect surfaceInsets) throws OutOfResourcesException {
    404         if (isRequested()) {
    405             // We lost the gl context, so recreate it.
    406             if (!isEnabled()) {
    407                 if (initialize(surface)) {
    408                     setup(width, height, attachInfo, surfaceInsets);
    409                     return true;
    410                 }
    411             }
    412         }
    413         return false;
    414     }
    415 
    416     /**
    417      * Updates the threaded renderer for the specified surface.
    418      *
    419      * @param surface The surface to render
    420      */
    421     void updateSurface(Surface surface) throws OutOfResourcesException {
    422         updateEnabledState(surface);
    423         setSurface(surface);
    424     }
    425 
    426     @Override
    427     public void setSurface(Surface surface) {
    428         // TODO: Do we ever pass a non-null but isValid() = false surface?
    429         // This is here to be super conservative for ViewRootImpl
    430         if (surface != null && surface.isValid()) {
    431             super.setSurface(surface);
    432         } else {
    433             super.setSurface(null);
    434         }
    435     }
    436 
    437     /**
    438      * Registers a callback to be executed when the next frame is being drawn on RenderThread. This
    439      * callback will be executed on a RenderThread worker thread, and only used for the next frame
    440      * and thus it will only fire once.
    441      *
    442      * @param callback The callback to register.
    443      */
    444     void registerRtFrameCallback(FrameDrawingCallback callback) {
    445         mNextRtFrameCallback = callback;
    446     }
    447 
    448     /**
    449      * Destroys all hardware rendering resources associated with the specified
    450      * view hierarchy.
    451      *
    452      * @param view The root of the view hierarchy
    453      */
    454     void destroyHardwareResources(View view) {
    455         destroyResources(view);
    456         clearContent();
    457     }
    458 
    459     private static void destroyResources(View view) {
    460         view.destroyHardwareResources();
    461     }
    462 
    463     /**
    464      * Sets up the renderer for drawing.
    465      *
    466      * @param width The width of the drawing surface.
    467      * @param height The height of the drawing surface.
    468      * @param attachInfo Information about the window.
    469      * @param surfaceInsets The drawing surface insets to apply
    470      */
    471     void setup(int width, int height, AttachInfo attachInfo, Rect surfaceInsets) {
    472         mWidth = width;
    473         mHeight = height;
    474 
    475         if (surfaceInsets != null && (surfaceInsets.left != 0 || surfaceInsets.right != 0
    476                 || surfaceInsets.top != 0 || surfaceInsets.bottom != 0)) {
    477             mHasInsets = true;
    478             mInsetLeft = surfaceInsets.left;
    479             mInsetTop = surfaceInsets.top;
    480             mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
    481             mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
    482 
    483             // If the surface has insets, it can't be opaque.
    484             setOpaque(false);
    485         } else {
    486             mHasInsets = false;
    487             mInsetLeft = 0;
    488             mInsetTop = 0;
    489             mSurfaceWidth = width;
    490             mSurfaceHeight = height;
    491         }
    492 
    493         mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
    494 
    495         setLightCenter(attachInfo);
    496     }
    497 
    498     /**
    499      * Updates the light position based on the position of the window.
    500      *
    501      * @param attachInfo Information about the window.
    502      */
    503     void setLightCenter(AttachInfo attachInfo) {
    504         // Adjust light position for window offsets.
    505         final Point displaySize = attachInfo.mPoint;
    506         attachInfo.mDisplay.getRealSize(displaySize);
    507         final float lightX = displaySize.x / 2f - attachInfo.mWindowLeft;
    508         final float lightY = mLightY - attachInfo.mWindowTop;
    509         setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
    510     }
    511 
    512     /**
    513      * Gets the current width of the surface. This is the width that the surface
    514      * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
    515      *
    516      * @return the current width of the surface
    517      */
    518     int getWidth() {
    519         return mWidth;
    520     }
    521 
    522     /**
    523      * Gets the current height of the surface. This is the height that the surface
    524      * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
    525      *
    526      * @return the current width of the surface
    527      */
    528     int getHeight() {
    529         return mHeight;
    530     }
    531 
    532     /**
    533      * Outputs extra debugging information in the specified file descriptor.
    534      */
    535     void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
    536         pw.flush();
    537         // If there's no arguments, eg 'dumpsys gfxinfo', then dump everything.
    538         // If there's a targetted package, eg 'dumpsys gfxinfo com.android.systemui', then only
    539         // dump the summary information
    540         int flags = (args == null || args.length == 0) ? FLAG_DUMP_ALL : 0;
    541         for (int i = 0; i < args.length; i++) {
    542             switch (args[i]) {
    543                 case "framestats":
    544                     flags |= FLAG_DUMP_FRAMESTATS;
    545                     break;
    546                 case "reset":
    547                     flags |= FLAG_DUMP_RESET;
    548                     break;
    549                 case "-a": // magic option passed when dumping a bugreport.
    550                     flags = FLAG_DUMP_ALL;
    551                     break;
    552             }
    553         }
    554         dumpProfileInfo(fd, flags);
    555     }
    556 
    557     Picture captureRenderingCommands() {
    558         return null;
    559     }
    560 
    561     @Override
    562     public boolean loadSystemProperties() {
    563         boolean changed = super.loadSystemProperties();
    564         if (changed) {
    565             invalidateRoot();
    566         }
    567         return changed;
    568     }
    569 
    570     private void updateViewTreeDisplayList(View view) {
    571         view.mPrivateFlags |= View.PFLAG_DRAWN;
    572         view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
    573                 == View.PFLAG_INVALIDATED;
    574         view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    575         view.updateDisplayListIfDirty();
    576         view.mRecreateDisplayList = false;
    577     }
    578 
    579     private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    580         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
    581         updateViewTreeDisplayList(view);
    582 
    583         // Consume and set the frame callback after we dispatch draw to the view above, but before
    584         // onPostDraw below which may reset the callback for the next frame.  This ensures that
    585         // updates to the frame callback during scroll handling will also apply in this frame.
    586         final FrameDrawingCallback callback = mNextRtFrameCallback;
    587         mNextRtFrameCallback = null;
    588         if (callback != null) {
    589             setFrameCallback(callback);
    590         }
    591 
    592         if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
    593             RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
    594             try {
    595                 final int saveCount = canvas.save();
    596                 canvas.translate(mInsetLeft, mInsetTop);
    597                 callbacks.onPreDraw(canvas);
    598 
    599                 canvas.enableZ();
    600                 canvas.drawRenderNode(view.updateDisplayListIfDirty());
    601                 canvas.disableZ();
    602 
    603                 callbacks.onPostDraw(canvas);
    604                 canvas.restoreToCount(saveCount);
    605                 mRootNodeNeedsUpdate = false;
    606             } finally {
    607                 mRootNode.endRecording();
    608             }
    609         }
    610         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    611     }
    612 
    613     /**
    614      * Interface used to receive callbacks whenever a view is drawn by
    615      * a threaded renderer instance.
    616      */
    617     interface DrawCallbacks {
    618         /**
    619          * Invoked before a view is drawn by a threaded renderer.
    620          * This method can be used to apply transformations to the
    621          * canvas but no drawing command should be issued.
    622          *
    623          * @param canvas The Canvas used to render the view.
    624          */
    625         void onPreDraw(RecordingCanvas canvas);
    626 
    627         /**
    628          * Invoked after a view is drawn by a threaded renderer.
    629          * It is safe to invoke drawing commands from this method.
    630          *
    631          * @param canvas The Canvas used to render the view.
    632          */
    633         void onPostDraw(RecordingCanvas canvas);
    634     }
    635 
    636     /**
    637      *  Indicates that the content drawn by DrawCallbacks needs to
    638      *  be updated, which will be done by the next call to draw()
    639      */
    640     void invalidateRoot() {
    641         mRootNodeNeedsUpdate = true;
    642     }
    643 
    644     /**
    645      * Draws the specified view.
    646      *
    647      * @param view The view to draw.
    648      * @param attachInfo AttachInfo tied to the specified view.
    649      */
    650     void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    651         final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    652         choreographer.mFrameInfo.markDrawStart();
    653 
    654         updateRootDisplayList(view, callbacks);
    655 
    656         // register animating rendernodes which started animating prior to renderer
    657         // creation, which is typical for animators started prior to first draw
    658         if (attachInfo.mPendingAnimatingRenderNodes != null) {
    659             final int count = attachInfo.mPendingAnimatingRenderNodes.size();
    660             for (int i = 0; i < count; i++) {
    661                 registerAnimatingRenderNode(
    662                         attachInfo.mPendingAnimatingRenderNodes.get(i));
    663             }
    664             attachInfo.mPendingAnimatingRenderNodes.clear();
    665             // We don't need this anymore as subsequent calls to
    666             // ViewRootImpl#attachRenderNodeAnimator will go directly to us.
    667             attachInfo.mPendingAnimatingRenderNodes = null;
    668         }
    669 
    670         int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    671         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
    672             setEnabled(false);
    673             attachInfo.mViewRootImpl.mSurface.release();
    674             // Invalidate since we failed to draw. This should fetch a Surface
    675             // if it is still needed or do nothing if we are no longer drawing
    676             attachInfo.mViewRootImpl.invalidate();
    677         }
    678         if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
    679             attachInfo.mViewRootImpl.invalidate();
    680         }
    681     }
    682 
    683     /** The root of everything */
    684     public @NonNull RenderNode getRootNode() {
    685         return mRootNode;
    686     }
    687 
    688     /**
    689      * Basic synchronous renderer. Currently only used to render the Magnifier, so use with care.
    690      * TODO: deduplicate against ThreadedRenderer.
    691      *
    692      * @hide
    693      */
    694     public static class SimpleRenderer extends HardwareRenderer {
    695         private final float mLightY, mLightZ, mLightRadius;
    696 
    697         public SimpleRenderer(final Context context, final String name, final Surface surface) {
    698             super();
    699             setName(name);
    700             setOpaque(false);
    701             setSurface(surface);
    702             final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
    703             mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
    704             mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
    705             mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
    706             final float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
    707             final float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
    708             a.recycle();
    709             setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha);
    710         }
    711 
    712         /**
    713          * Set the light center.
    714          */
    715         public void setLightCenter(final Display display,
    716                 final int windowLeft, final int windowTop) {
    717             // Adjust light position for window offsets.
    718             final Point displaySize = new Point();
    719             display.getRealSize(displaySize);
    720             final float lightX = displaySize.x / 2f - windowLeft;
    721             final float lightY = mLightY - windowTop;
    722 
    723             setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
    724         }
    725 
    726         public RenderNode getRootNode() {
    727             return mRootNode;
    728         }
    729 
    730         /**
    731          * Draw the surface.
    732          */
    733         public void draw(final FrameDrawingCallback callback) {
    734             final long vsync = AnimationUtils.currentAnimationTimeMillis() * 1000000L;
    735             if (callback != null) {
    736                 setFrameCallback(callback);
    737             }
    738             createRenderRequest()
    739                     .setVsyncTime(vsync)
    740                     .syncAndDraw();
    741         }
    742     }
    743 }
    744