Enumerate initial version of VK_GOOGLE_display_timing

The implementation seems correct with the exception that it is not obtaining
the refresh durations from SurfaceFlinger/HAL.  A maximum of 10 TimingInfo's
(containing the timing for previous presents) are stored, and timestamps are
only queried from SurfaceFlinger for presents (a.k.a. frames) that occured 5
presents/frames ago.

Test: Manually tested with a modified cube demo, that changes its timing as a
result of the data returned from this extension.

Change-Id: I199614ed58877b64eab2568a76ba517acca3bf87
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 296be2d..bad5645 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -20,6 +20,7 @@
 #include <gui/BufferQueue.h>
 #include <sync/sync.h>
 #include <utils/StrongPointer.h>
+#include <utils/SortedVector.h>
 
 #include "driver.h"
 
@@ -105,6 +106,66 @@
     }
 }
 
+class TimingInfo {
+   public:
+    TimingInfo() {}
+    TimingInfo(const VkPresentTimeGOOGLE* qp) {
+        vals_.presentID = qp->presentID;
+        vals_.desiredPresentTime = qp->desiredPresentTime;
+        timestamp_desired_present_time_ = 0;
+        timestamp_actual_present_time_ = 0;
+        timestamp_render_complete_time_ = 0;
+        timestamp_composition_latch_time_ = 0;
+    }
+    bool ready() {
+        return (timestamp_desired_present_time_ &&
+                timestamp_actual_present_time_ &&
+                timestamp_render_complete_time_ &&
+                timestamp_composition_latch_time_);
+    }
+    void calculate(uint64_t rdur) {
+        vals_.actualPresentTime = timestamp_actual_present_time_;
+        uint64_t margin = (timestamp_composition_latch_time_ -
+                           timestamp_render_complete_time_);
+        // Calculate vals_.earliestPresentTime, and potentially adjust
+        // vals_.presentMargin.  The initial value of vals_.earliestPresentTime
+        // is vals_.actualPresentTime.  If we can subtract rdur (the duration
+        // of a refresh cycle) from vals_.earliestPresentTime (and also from
+        // vals_.presentMargin) and still leave a positive margin, then we can
+        // report to the application that it could have presented earlier than
+        // it did (per the extension specification).  If for some reason, we
+        // can do this subtraction repeatedly, we do, since
+        // vals_.earliestPresentTime really is supposed to be the "earliest".
+        uint64_t early_time = vals_.actualPresentTime;
+        while ((margin > rdur) &&
+               ((early_time - rdur) > timestamp_composition_latch_time_)) {
+            early_time -= rdur;
+            margin -= rdur;
+        }
+        vals_.earliestPresentTime = early_time;
+        vals_.presentMargin = margin;
+    }
+    void get_values(VkPastPresentationTimingGOOGLE* values) { *values = vals_; }
+
+   public:
+    VkPastPresentationTimingGOOGLE vals_;
+
+    uint64_t timestamp_desired_present_time_;
+    uint64_t timestamp_actual_present_time_;
+    uint64_t timestamp_render_complete_time_;
+    uint64_t timestamp_composition_latch_time_;
+};
+
+static inline int compare_type(const TimingInfo& lhs, const TimingInfo& rhs) {
+    // TODO(ianelliott): Change this from presentID to the frame ID once
+    // brianderson lands the appropriate patch:
+    if (lhs.vals_.presentID < rhs.vals_.presentID)
+        return -1;
+    if (lhs.vals_.presentID > rhs.vals_.presentID)
+        return 1;
+    return 0;
+}
+
 // ----------------------------------------------------------------------------
 
 struct Surface {
@@ -120,11 +181,19 @@
     return reinterpret_cast<Surface*>(handle);
 }
 
+// Maximum number of TimingInfo structs to keep per swapchain:
+enum { MAX_TIMING_INFOS = 10 };
+// Minimum number of frames to look for in the past (so we don't cause
+// syncronous requests to Surface Flinger):
+enum { MIN_NUM_FRAMES_AGO = 5 };
+
 struct Swapchain {
     Swapchain(Surface& surface_, uint32_t num_images_)
         : surface(surface_),
           num_images(num_images_),
-          frame_timestamps_enabled(false) {}
+          frame_timestamps_enabled(false) {
+        timing.clear();
+    }
 
     Surface& surface;
     uint32_t num_images;
@@ -141,6 +210,8 @@
         int dequeue_fence;
         bool dequeued;
     } images[android::BufferQueue::NUM_BUFFER_SLOTS];
+
+    android::SortedVector<TimingInfo> timing;
 };
 
 VkSwapchainKHR HandleFromSwapchain(Swapchain* swapchain) {
@@ -208,6 +279,112 @@
             ReleaseSwapchainImage(device, nullptr, -1, swapchain->images[i]);
     }
     swapchain->surface.swapchain_handle = VK_NULL_HANDLE;
+    swapchain->timing.clear();
+}
+
+uint32_t get_num_ready_timings(Swapchain& swapchain) {
+    uint32_t num_ready = 0;
+    uint32_t num_timings = static_cast<uint32_t>(swapchain.timing.size());
+    uint32_t frames_ago = num_timings;
+    for (uint32_t i = 0; i < num_timings; i++) {
+        TimingInfo* ti = &swapchain.timing.editItemAt(i);
+        if (ti) {
+            if (ti->ready()) {
+                // This TimingInfo is ready to be reported to the user.  Add it
+                // to the num_ready.
+                num_ready++;
+            } else {
+                // This TimingInfo is not yet ready to be reported to the user,
+                // and so we should look for any available timestamps that
+                // might make it ready.
+                int64_t desired_present_time = 0;
+                int64_t render_complete_time = 0;
+                int64_t composition_latch_time = 0;
+                int64_t actual_present_time = 0;
+                for (uint32_t f = MIN_NUM_FRAMES_AGO; f < frames_ago; f++) {
+                    // Obtain timestamps:
+                    int ret = native_window_get_frame_timestamps(
+                        swapchain.surface.window.get(), f,
+                        &desired_present_time, &render_complete_time,
+                        &composition_latch_time,
+                        NULL,  //&first_composition_start_time,
+                        NULL,  //&last_composition_start_time,
+                        NULL,  //&composition_finish_time,
+                        // TODO(ianelliott): Maybe ask if this one is
+                        // supported, at startup time (since it may not be
+                        // supported):
+                        &actual_present_time,
+                        NULL,  //&display_retire_time,
+                        NULL,  //&dequeue_ready_time,
+                        NULL /*&reads_done_time*/);
+                    if (ret) {
+                        break;
+                    } else if (!ret) {
+                        // We obtained at least one valid timestamp.  See if it
+                        // is for the present represented by this TimingInfo:
+                        if (static_cast<uint64_t>(desired_present_time) ==
+                            ti->vals_.desiredPresentTime) {
+                            // Record the timestamp(s) we received, and then
+                            // see if this TimingInfo is ready to be reported
+                            // to the user:
+                            ti->timestamp_desired_present_time_ =
+                                static_cast<uint64_t>(desired_present_time);
+                            ti->timestamp_actual_present_time_ =
+                                static_cast<uint64_t>(actual_present_time);
+                            ti->timestamp_render_complete_time_ =
+                                static_cast<uint64_t>(render_complete_time);
+                            ti->timestamp_composition_latch_time_ =
+                                static_cast<uint64_t>(composition_latch_time);
+
+                            if (ti->ready()) {
+                                // The TimingInfo has received enough
+                                // timestamps, and should now use those
+                                // timestamps to calculate the info that should
+                                // be reported to the user:
+                                //
+                                // FIXME: GET ACTUAL VALUE RATHER THAN HARD-CODE
+                                // IT:
+                                ti->calculate(16666666);
+                                num_ready++;
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return num_ready;
+}
+
+// TODO(ianelliott): DEAL WITH RETURN VALUE (e.g. VK_INCOMPLETE)!!!
+void copy_ready_timings(Swapchain& swapchain,
+                        uint32_t* count,
+                        VkPastPresentationTimingGOOGLE* timings) {
+    uint32_t num_copied = 0;
+    uint32_t num_timings = static_cast<uint32_t>(swapchain.timing.size());
+    if (*count < num_timings) {
+        num_timings = *count;
+    }
+    for (uint32_t i = 0; i < num_timings; i++) {
+        TimingInfo* ti = &swapchain.timing.editItemAt(i);
+        if (ti && ti->ready()) {
+            ti->get_values(&timings[num_copied]);
+            num_copied++;
+            // We only report the values for a given present once, so remove
+            // them from swapchain.timing:
+            //
+            // TODO(ianelliott): SEE WHAT HAPPENS TO THE LOOP WHEN THE
+            // FOLLOWING IS DONE:
+            swapchain.timing.removeAt(i);
+            i--;
+            num_timings--;
+            if (*count == num_copied) {
+                break;
+            }
+        }
+    }
+    *count = num_copied;
 }
 
 }  // anonymous namespace
@@ -990,16 +1167,31 @@
                 }
                 if (time) {
                     if (!swapchain.frame_timestamps_enabled) {
+                        ALOGV(
+                            "Calling "
+                            "native_window_enable_frame_timestamps(true)");
                         native_window_enable_frame_timestamps(window, true);
                         swapchain.frame_timestamps_enabled = true;
                     }
-                    // TODO(ianelliott): need to store the presentID (and
-                    // desiredPresentTime), so it can be later correlated to
-                    // this present.  Probably modify the following function
-                    // (and below) to plumb a path to store it in FrameEvents
-                    // code, on the producer side.
-                    native_window_set_buffers_timestamp(
-                        window, static_cast<int64_t>(time->desiredPresentTime));
+                    // Record this presentID and desiredPresentTime so it can
+                    // be later correlated to this present.
+                    TimingInfo timing_record(time);
+                    swapchain.timing.add(timing_record);
+                    uint32_t num_timings =
+                        static_cast<uint32_t>(swapchain.timing.size());
+                    if (num_timings > MAX_TIMING_INFOS) {
+                        swapchain.timing.removeAt(0);
+                    }
+                    if (time->desiredPresentTime) {
+                        // Set the desiredPresentTime:
+                        ALOGV(
+                            "Calling "
+                            "native_window_set_buffers_timestamp(%" PRId64 ")",
+                            time->desiredPresentTime);
+                        native_window_set_buffers_timestamp(
+                            window,
+                            static_cast<int64_t>(time->desiredPresentTime));
+                    }
                 }
                 err = window->queueBuffer(window, img.buffer.get(), fence);
                 // queueBuffer always closes fence, even on error
@@ -1046,8 +1238,8 @@
     VkResult result = VK_SUCCESS;
 
     // TODO(ianelliott): FULLY IMPLEMENT THIS FUNCTION!!!
-    pDisplayTimingProperties->minRefreshDuration = 16666666666;
-    pDisplayTimingProperties->maxRefreshDuration = 16666666666;
+    pDisplayTimingProperties->minRefreshDuration = 16666666;
+    pDisplayTimingProperties->maxRefreshDuration = 16666666;
 
     return result;
 }
@@ -1063,15 +1255,16 @@
     VkResult result = VK_SUCCESS;
 
     if (!swapchain.frame_timestamps_enabled) {
+        ALOGV("Calling native_window_enable_frame_timestamps(true)");
         native_window_enable_frame_timestamps(window, true);
         swapchain.frame_timestamps_enabled = true;
     }
 
-    // TODO(ianelliott): FULLY IMPLEMENT THIS FUNCTION!!!
     if (timings) {
-        *count = 0;
+        // TODO(ianelliott): plumb return value (e.g. VK_INCOMPLETE)
+        copy_ready_timings(swapchain, count, timings);
     } else {
-        *count = 0;
+        *count = get_num_ready_timings(swapchain);
     }
 
     return result;