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