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