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