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