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