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.
isAvailable()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      */
disable(boolean system)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      */
enableForegroundTrimming()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      */
isAvailable()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      */
create(Context context, boolean translucent, String name)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 
ThreadedRenderer(Context context, boolean translucent, String name)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
destroy()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      */
isEnabled()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      */
setEnabled(boolean enabled)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      */
isRequested()351     boolean isRequested() {
352         return mRequested;
353     }
354 
355     /**
356      * Indicates whether threaded rendering is currently requested but not
357      * necessarily enabled yet.
358      */
setRequested(boolean requested)359     void setRequested(boolean requested) {
360         mRequested = requested;
361     }
362 
updateEnabledState(Surface surface)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      */
initialize(Surface surface)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      */
initializeIfNeeded(int width, int height, View.AttachInfo attachInfo, Surface surface, Rect surfaceInsets)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      */
updateSurface(Surface surface)421     void updateSurface(Surface surface) throws OutOfResourcesException {
422         updateEnabledState(surface);
423         setSurface(surface);
424     }
425 
426     @Override
setSurface(Surface surface)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      */
registerRtFrameCallback(FrameDrawingCallback callback)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      */
destroyHardwareResources(View view)454     void destroyHardwareResources(View view) {
455         destroyResources(view);
456         clearContent();
457     }
458 
destroyResources(View view)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      */
setup(int width, int height, AttachInfo attachInfo, Rect surfaceInsets)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      */
setLightCenter(AttachInfo attachInfo)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      */
getWidth()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      */
getHeight()528     int getHeight() {
529         return mHeight;
530     }
531 
532     /**
533      * Outputs extra debugging information in the specified file descriptor.
534      */
dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args)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 
captureRenderingCommands()557     Picture captureRenderingCommands() {
558         return null;
559     }
560 
561     @Override
loadSystemProperties()562     public boolean loadSystemProperties() {
563         boolean changed = super.loadSystemProperties();
564         if (changed) {
565             invalidateRoot();
566         }
567         return changed;
568     }
569 
updateViewTreeDisplayList(View view)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 
updateRootDisplayList(View view, DrawCallbacks callbacks)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          */
onPreDraw(RecordingCanvas canvas)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          */
onPostDraw(RecordingCanvas canvas)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      */
invalidateRoot()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      */
draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks)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 */
getRootNode()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 
SimpleRenderer(final Context context, final String name, final Surface surface)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          */
setLightCenter(final Display display, final int windowLeft, final int windowTop)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 
getRootNode()726         public RenderNode getRootNode() {
727             return mRootNode;
728         }
729 
730         /**
731          * Draw the surface.
732          */
draw(final FrameDrawingCallback callback)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