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