Eliminate recents upload jank

Bug: 30342017

Upload recents thumbnails in the
dead gaps between frames instead of
at the start of a frame. This eliminates
jank caused by the large texture
upload.

Change-Id: I507cd286d199109c7a9a1511d68ba5ab5d28069f
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index d681246..d7550a4 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -18,6 +18,7 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include <Caches.h>
 #include <hwui/Paint.h>
+#include <renderthread/RenderProxy.h>
 
 #include "core_jni_helpers.h"
 
@@ -1361,6 +1362,14 @@
     return reinterpret_cast<jlong>(pixelRef);
 }
 
+static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) {
+    LocalScopedBitmap bitmapHandle(bitmapPtr);
+    if (!bitmapHandle.valid()) return;
+    SkBitmap bitmap;
+    bitmapHandle->getSkBitmap(&bitmap);
+    android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmap);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gBitmapMethods[] = {
@@ -1404,6 +1413,7 @@
                                             (void*)Bitmap_copyPixelsFromBuffer },
     {   "nativeSameAs",             "(JJ)Z", (void*)Bitmap_sameAs },
     {   "nativeRefPixelRef",        "(J)J", (void*)Bitmap_refPixelRef },
+    {   "nativePrepareToDraw",      "(J)V", (void*)Bitmap_prepareToDraw },
 };
 
 int register_android_graphics_Bitmap(JNIEnv* env)
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 1fdc1f5..49721cf 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1667,10 +1667,10 @@
      * and therefore is harmless.
      */
     public void prepareToDraw() {
-        // TODO: Consider having this start an async upload?
-        // With inPurgeable no-op'd there's currently no use for this
-        // method, but it could have interesting future uses.
         checkRecycled("Can't prepareToDraw on a recycled bitmap!");
+        // Kick off an update/upload of the bitmap outside of the normal
+        // draw path.
+        nativePrepareToDraw(mNativePtr);
     }
 
     /**
@@ -1741,4 +1741,5 @@
     private static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap);
     private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1);
     private static native long nativeRefPixelRef(long nativeBitmap);
+    private static native void nativePrepareToDraw(long nativeBitmap);
 }
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index ade8600..523924a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -167,6 +167,10 @@
     return texture;
 }
 
+bool TextureCache::prefetch(const SkBitmap* bitmap) {
+    return getCachedTexture(bitmap, AtlasUsageType::Use);
+}
+
 Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
     Texture* texture = getCachedTexture(bitmap, atlasUsageType);
 
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index a4317ce..0a61b6b 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -78,6 +78,13 @@
     bool prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap);
 
     /**
+     * Attempts to precache the SkBitmap. Returns true if a Texture was successfully
+     * acquired for the bitmap, false otherwise. Does not mark the Texture
+     * as in use and won't update currently in-use Textures.
+     */
+    bool prefetch(const SkBitmap* bitmap);
+
+    /**
      * Returns the texture associated with the specified bitmap from either within the cache, or
      * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated.
      */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 06a24b2..fb1c896 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -22,9 +22,11 @@
 #include "Readback.h"
 #include "Rect.h"
 #include "renderthread/CanvasContext.h"
+#include "renderthread/EglManager.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 #include "utils/Macros.h"
+#include "utils/TimeUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -44,6 +46,8 @@
     typedef struct { \
         a1; a2; a3; a4; a5; a6; a7; a8; \
     } ARGS(name); \
+    static_assert(std::is_trivially_destructible<ARGS(name)>::value, \
+            "Error, ARGS must be trivially destructible!"); \
     static void* Bridge_ ## name(ARGS(name)* args)
 
 #define SETUP_TASK(method) \
@@ -636,6 +640,41 @@
             reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
 }
 
+CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, SkBitmap* bitmap) {
+    if (Caches::hasInstance() && args->thread->eglManager().hasEglContext()) {
+        ATRACE_NAME("Bitmap#prepareToDraw task");
+        Caches::getInstance().textureCache.prefetch(args->bitmap);
+    }
+    delete args->bitmap;
+    args->bitmap = nullptr;
+    return nullptr;
+}
+
+void RenderProxy::prepareToDraw(const SkBitmap& bitmap) {
+    // If we haven't spun up a hardware accelerated window yet, there's no
+    // point in precaching these bitmaps as it can't impact jank.
+    // We also don't know if we even will spin up a hardware-accelerated
+    // window or not.
+    if (!RenderThread::hasInstance()) return;
+    RenderThread* renderThread = &RenderThread::getInstance();
+    SETUP_TASK(prepareToDraw);
+    args->thread = renderThread;
+    args->bitmap = new SkBitmap(bitmap);
+    nsecs_t lastVsync = renderThread->timeLord().latestVsync();
+    nsecs_t estimatedNextVsync = lastVsync + renderThread->timeLord().frameIntervalNanos();
+    nsecs_t timeToNextVsync = estimatedNextVsync - systemTime(CLOCK_MONOTONIC);
+    // We expect the UI thread to take 4ms and for RT to be active from VSYNC+4ms to
+    // VSYNC+12ms or so, so aim for the gap during which RT is expected to
+    // be idle
+    // TODO: Make this concept a first-class supported thing? RT could use
+    // knowledge of pending draws to better schedule this task
+    if (timeToNextVsync > -6_ms && timeToNextVsync < 1_ms) {
+        renderThread->queueAt(task, estimatedNextVsync + 8_ms);
+    } else {
+        renderThread->queue(task);
+    }
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e31062c8..bb111bd 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -129,6 +129,7 @@
     ANDROID_API long getDroppedFrameReportCount();
 
     ANDROID_API static int copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap);
+    ANDROID_API static void prepareToDraw(const SkBitmap& bitmap);
 
 private:
     RenderThread& mRenderThread;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ca59831..ba31e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -200,6 +200,10 @@
 
                             if (cachedThumbnailData.thumbnail == null) {
                                 cachedThumbnailData.thumbnail = mDefaultThumbnail;
+                            } else {
+                                // Kick off an early upload of the bitmap to GL so
+                                // that this won't jank the first frame it's drawn in.
+                                cachedThumbnailData.thumbnail.prepareToDraw();
                             }
 
                             // When svelte, we trim the memory to just the visible thumbnails when
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 3193759..c46adf1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -164,6 +164,7 @@
     /** Sets the thumbnail to a given bitmap. */
     void setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
         if (bm != null) {
+            bm.prepareToDraw();
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight());