Add support for render-ahead

For periods of time during which latency is less important
allow a client to request a deeper render-ahead pipeline.

The latency tradeoff results in less overall visual jank

Test: none, only used by macrobench

Change-Id: I516203b70bdc75b6415fa08bf9c4fb1b598b0102
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 0a6c45b..7b14322 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -36,6 +36,7 @@
 bool Properties::skipEmptyFrames = true;
 bool Properties::useBufferAge = true;
 bool Properties::enablePartialUpdates = true;
+bool Properties::usePresentTime = true;
 
 DebugLevel Properties::debugLevel = kDebugDisabled;
 OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -135,6 +136,7 @@
     skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
     useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
     enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
+    usePresentTime = property_get_bool(PROPERTY_USE_PRESENT_TIME, true);
 
     filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 764c502..1a0bdfd 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -151,6 +151,12 @@
  */
 #define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.use_partial_updates"
 
+/**
+ * Setting this to "false" will disable the use of the EGL_ANDROID_presentation_time extension
+ * and prevents more precise control over swap behavior & timings.
+ */
+#define PROPERTY_USE_PRESENT_TIME "debug.hwui.use_present_time"
+
 #define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead"
 
 /**
@@ -220,6 +226,7 @@
     static bool skipEmptyFrames;
     static bool useBufferAge;
     static bool enablePartialUpdates;
+    static bool usePresentTime;
 
     // TODO: Move somewhere else?
     static constexpr float textGamma = 1.45f;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f4d8051..609d26c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -17,6 +17,7 @@
 #include "CanvasContext.h"
 #include <GpuMemoryTracker.h>
 
+#include "../Properties.h"
 #include "AnimationContext.h"
 #include "Caches.h"
 #include "EglManager.h"
@@ -34,7 +35,6 @@
 #include "renderstate/Stencil.h"
 #include "utils/GLUtils.h"
 #include "utils/TimeUtils.h"
-#include "../Properties.h"
 
 #include <cutils/properties.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
@@ -194,6 +194,7 @@
     if (hasSurface) {
         mHaveNewSurface = true;
         mSwapHistory.clear();
+        updateBufferCount();
     } else {
         mRenderThread.removeFrameCallback(this);
     }
@@ -427,6 +428,9 @@
 
     waitOnFences();
 
+    frame.setPresentTime(mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
+                         (mRenderThread.timeLord().frameIntervalNanos() * (mRenderAheadDepth + 1)));
+
     bool requireSwap = false;
     bool didSwap =
             mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
@@ -705,6 +709,26 @@
     return mFrameNumber;
 }
 
+void overrideBufferCount(const sp<Surface>& surface, int bufferCount) {
+    struct SurfaceExposer : Surface {
+        using Surface::setBufferCount;
+    };
+    // Protected is just a sign, not a cop
+    ((*surface.get()).*&SurfaceExposer::setBufferCount)(bufferCount);
+}
+
+void CanvasContext::updateBufferCount() {
+    overrideBufferCount(mNativeSurface, 3 + mRenderAheadDepth);
+}
+
+void CanvasContext::setRenderAheadDepth(int renderAhead) {
+    if (renderAhead < 0 || renderAhead > 2 || renderAhead == mRenderAheadDepth) {
+        return;
+    }
+    mRenderAheadDepth = renderAhead;
+    updateBufferCount();
+}
+
 SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
     if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
         // can't rely on prior content of window if viewport size changes
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index c2cc72a..8aaa8c1 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -78,7 +78,7 @@
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator,
                              ErrorHandler* errorHandler) {
         return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, mWideColorGamut,
-                errorHandler);
+                                                    errorHandler);
     }
 
     /**
@@ -190,6 +190,8 @@
 
     IRenderPipeline* getRenderPipeline() { return mRenderPipeline.get(); }
 
+    void setRenderAheadDepth(int renderAhead);
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                   IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
@@ -202,6 +204,7 @@
     void freePrefetchedLayers();
 
     bool isSwapChainStuffed();
+    void updateBufferCount();
 
     SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
 
@@ -227,6 +230,7 @@
 
     RingBuffer<SwapHistory, 3> mSwapHistory;
     int64_t mFrameNumber = -1;
+    int mRenderAheadDepth = 0;
 
     // last vsync for a dropped frame due to stuffed queue
     nsecs_t mLastDropVsync = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 778e768..5b0e281 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -103,9 +103,8 @@
 
     // Even if we aren't drawing this vsync pulse the next frame number will still be accurate
     if (CC_UNLIKELY(callback)) {
-        context->enqueueFrameWork([callback, frameNr = context->getFrameNumber()]() {
-            callback(frameNr);
-        });
+        context->enqueueFrameWork(
+                [ callback, frameNr = context->getFrameNumber() ]() { callback(frameNr); });
     }
 
     if (CC_LIKELY(canDrawThisFrame)) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 6e239e3..3a49beb 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -83,6 +83,7 @@
     bool glColorSpace = false;
     bool scRGB = false;
     bool contextPriority = false;
+    bool presentTime = false;
 } EglExtensions;
 
 EglManager::EglManager(RenderThread& thread)
@@ -170,6 +171,7 @@
     EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb");
 #endif
     EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority");
+    EglExtensions.presentTime = extensions.has("EGL_ANDROID_presentation_time");
 }
 
 bool EglManager::hasEglContext() {
@@ -242,7 +244,7 @@
                              &numConfigs) ||
             numConfigs != 1) {
             ALOGE("Device claims wide gamut support, cannot find matching config, error = %s",
-                    eglErrorString());
+                  eglErrorString());
             EglExtensions.pixelFormatFloat = false;
         }
     }
@@ -437,6 +439,13 @@
         fence();
     }
 
+    if (EglExtensions.presentTime && Properties::usePresentTime) {
+        if (!eglPresentationTimeANDROID(mEglDisplay, frame.mSurface, frame.mPresentTime)) {
+            LOG_ALWAYS_FATAL("Failed to set presentation time on surface %p, error=%s",
+                             (void*)frame.mSurface, eglErrorString());
+        }
+    }
+
     EGLint rects[4];
     frame.map(screenDirty, rects);
     eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects, screenDirty.isEmpty() ? 0 : 1);
diff --git a/libs/hwui/renderthread/Frame.h b/libs/hwui/renderthread/Frame.h
index d266faa..03cf95d 100644
--- a/libs/hwui/renderthread/Frame.h
+++ b/libs/hwui/renderthread/Frame.h
@@ -37,6 +37,8 @@
     // for what this means
     int32_t bufferAge() const { return mBufferAge; }
 
+    void setPresentTime(int64_t presentTime) { mPresentTime = presentTime; }
+
 private:
     Frame() {}
     friend class EglManager;
@@ -44,6 +46,7 @@
     int32_t mWidth;
     int32_t mHeight;
     int32_t mBufferAge;
+    int64_t mPresentTime;
 
     EGLSurface mSurface;
 
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index 876af47..1925808 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -22,8 +22,8 @@
 #include "GlLayer.h"
 #include "OpenGLReadback.h"
 #include "ProfileRenderer.h"
-#include "renderstate/RenderState.h"
 #include "TreeInfo.h"
+#include "renderstate/RenderState.h"
 
 #include <cutils/properties.h>
 #include <strings.h>
@@ -203,8 +203,7 @@
 
 bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node,
                                          const DamageAccumulator& damageAccumulator,
-                                         bool wideColorGamut,
-                                         ErrorHandler* errorHandler) {
+                                         bool wideColorGamut, ErrorHandler* errorHandler) {
     RenderState& renderState = mRenderThread.renderState();
     OffscreenBufferPool& layerPool = renderState.layerPool();
     bool transformUpdateNeeded = false;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index c6a9b55f..00645d6 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -290,6 +290,12 @@
     });
 }
 
+void RenderProxy::setRenderAheadDepth(int renderAhead) {
+    mRenderThread.queue().post([ context = mContext, renderAhead ]() {
+        context->setRenderAheadDepth(renderAhead);
+    });
+}
+
 int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
                                  SkBitmap* bitmap) {
     auto& thread = RenderThread::getInstance();
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 3425c5c..bd5c9a9 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -119,7 +119,23 @@
 
     ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer);
     ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
-    ANDROID_API long getDroppedFrameReportCount();
+
+    /**
+     * Sets a render-ahead depth on the backing renderer. This will increase latency by
+     * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>.
+     * In return the renderer will be less susceptible to jitter, resulting in a smoother animation.
+     *
+     * Not recommended to use in response to anything touch driven, but for canned animations
+     * where latency is not a concern careful use may be beneficial.
+     *
+     * Note that when increasing this there will be a frame gap of N frames where N is
+     * renderAhead - <current renderAhead>. When decreasing this if there are any pending
+     * frames they will retain their prior renderAhead value, so it will take a few frames
+     * for the decrease to flush through.
+     *
+     * @param renderAhead How far to render ahead, must be in the range [0..2]
+     */
+    ANDROID_API void setRenderAheadDepth(int renderAhead);
 
     ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right,
                                            int bottom, SkBitmap* bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6a2a025..4b154e6 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -25,8 +25,8 @@
 #include "hwui/Bitmap.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaOpenGLReadback.h"
-#include "pipeline/skia/SkiaVulkanReadback.h"
 #include "pipeline/skia/SkiaVulkanPipeline.h"
+#include "pipeline/skia/SkiaVulkanReadback.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/OpenGLPipeline.h"
 #include "utils/FatVector.h"
@@ -93,14 +93,11 @@
     DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
 
     virtual void requestNextVsync() override {
-        mRenderThread->queue().postDelayed(16_ms, [this]() {
-            mRenderThread->drainDisplayEventQueue();
-        });
+        mRenderThread->queue().postDelayed(16_ms,
+                                           [this]() { mRenderThread->drainDisplayEventQueue(); });
     }
 
-    virtual nsecs_t latestVsyncEvent() override {
-        return systemTime(CLOCK_MONOTONIC);
-    }
+    virtual nsecs_t latestVsyncEvent() override { return systemTime(CLOCK_MONOTONIC); }
 
 private:
     RenderThread* mRenderThread;
@@ -147,13 +144,13 @@
         auto receiver = std::make_unique<DisplayEventReceiver>();
         status_t status = receiver->initCheck();
         LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
-                "Initialization of DisplayEventReceiver "
-                        "failed with status: %d",
-                status);
+                            "Initialization of DisplayEventReceiver "
+                            "failed with status: %d",
+                            status);
 
         // Register the FD
         mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT,
-                RenderThread::displayEventReceiverCallback, this);
+                       RenderThread::displayEventReceiverCallback, this);
         mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver));
     } else {
         mVsyncSource = new DummyVsyncSource(this);
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 91022cf..74a039b 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -38,6 +38,7 @@
         int count = 0;
         int reportFrametimeWeight = 0;
         bool renderOffscreen = true;
+        int renderAhead = 0;
     };
 
     template <class T>
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 9428f53..854a449 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -153,6 +153,12 @@
     proxy->resetProfileInfo();
     proxy->fence();
 
+    if (opts.renderAhead) {
+        // Need to let the queue drain to see render-ahead in action.
+        usleep(33000);
+    }
+    proxy->setRenderAheadDepth(opts.renderAhead);
+
     ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
 
     nsecs_t start = systemTime(CLOCK_MONOTONIC);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 0a9a74e..4cb5cfd 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -68,6 +68,7 @@
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
   --renderer=TYPE      Sets the render pipeline to use. May be opengl, skiagl, or skiavk
+  --render-ahead=NUM   Sets how far to render-ahead. Must be 0 (default), 1, or 2.
 )");
 }
 
@@ -173,6 +174,7 @@
     Onscreen,
     Offscreen,
     Renderer,
+    RenderAhead,
 };
 }
 
@@ -188,6 +190,7 @@
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
+        {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -286,6 +289,16 @@
                 gOpts.renderOffscreen = true;
                 break;
 
+            case LongOpts::RenderAhead:
+                if (!optarg) {
+                    error = true;
+                }
+                gOpts.renderAhead = atoi(optarg);
+                if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) {
+                    error = true;
+                }
+                break;
+
             case 'h':
                 printHelp();
                 exit(EXIT_SUCCESS);