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