[scenic] Allow setting frame time lower bound on scheduler via config

This CL allows us to specify a lower bound for frame time predictions
made in WindowedFramePredictor. This can be set via
/config/data/frame_scheduler_min_frame_time_in_us. Default is kept as
0 so that there is no change in current behavior.

Bug: 24710, 24606
Change-Id: I4cc274a63c30c7def851f3ab6aa05c7e027740c0
diff --git a/src/ui/scenic/bin/app.cc b/src/ui/scenic/bin/app.cc
index 58b8b6e..24dd95f 100644
--- a/src/ui/scenic/bin/app.cc
+++ b/src/ui/scenic/bin/app.cc
@@ -18,6 +18,7 @@
 #endif
 
 #include "src/lib/cobalt/cpp/cobalt_logger.h"
+#include "src/lib/files/file.h"
 #include "src/ui/scenic/lib/gfx/api/internal_snapshot_impl.h"
 #include "src/ui/scenic/lib/scheduling/default_frame_scheduler.h"
 #include "src/ui/scenic/lib/scheduling/frame_metrics_registry.cb.h"
@@ -47,6 +48,23 @@
     return nullptr;
   };
 };
+
+zx::duration GetMinimumPredictedFrameDuration() {
+  std::string frame_scheduler_min_predicted_frame_duration;
+  int frame_scheduler_min_predicted_frame_duration_in_us = 0;
+  if (files::ReadFileToString("/config/data/frame_scheduler_min_predicted_frame_duration_in_us",
+                              &frame_scheduler_min_predicted_frame_duration)) {
+    frame_scheduler_min_predicted_frame_duration_in_us =
+        atoi(frame_scheduler_min_predicted_frame_duration.c_str());
+    FXL_DCHECK(frame_scheduler_min_predicted_frame_duration_in_us >= 0);
+    FXL_LOG(INFO) << "min_predicted_frame_duration(us): "
+                  << frame_scheduler_min_predicted_frame_duration_in_us;
+  }
+  return frame_scheduler_min_predicted_frame_duration_in_us > 0
+             ? zx::usec(frame_scheduler_min_predicted_frame_duration_in_us)
+             : scheduling::DefaultFrameScheduler::kMinPredictedFrameDuration;
+}
+
 }  // namespace
 
 namespace scenic_impl {
@@ -147,9 +165,11 @@
   if (cobalt_logger == nullptr) {
     FX_LOGS(ERROR) << "CobaltLogger creation failed!";
   }
+
   frame_scheduler_ = std::make_shared<scheduling::DefaultFrameScheduler>(
       display->vsync_timing(),
       std::make_unique<scheduling::WindowedFramePredictor>(
+          GetMinimumPredictedFrameDuration(),
           scheduling::DefaultFrameScheduler::kInitialRenderDuration,
           scheduling::DefaultFrameScheduler::kInitialUpdateDuration),
       scenic_.inspect_node()->CreateChild("FrameScheduler"), std::move(cobalt_logger));
diff --git a/src/ui/scenic/bin/meta/scenic.cmx b/src/ui/scenic/bin/meta/scenic.cmx
index b301e71..ae3cea3 100644
--- a/src/ui/scenic/bin/meta/scenic.cmx
+++ b/src/ui/scenic/bin/meta/scenic.cmx
@@ -7,6 +7,7 @@
             "class/display-controller"
         ],
         "features": [
+            "config-data",
             "vulkan",
             "isolated-temp"
         ],
diff --git a/src/ui/scenic/lib/gfx/tests/gfx_test.cc b/src/ui/scenic/lib/gfx/tests/gfx_test.cc
index 3e1f502..8201d0b 100644
--- a/src/ui/scenic/lib/gfx/tests/gfx_test.cc
+++ b/src/ui/scenic/lib/gfx/tests/gfx_test.cc
@@ -26,6 +26,7 @@
   frame_scheduler_ = std::make_shared<scheduling::DefaultFrameScheduler>(
       std::make_shared<scheduling::VsyncTiming>(),
       std::make_unique<scheduling::WindowedFramePredictor>(
+          scheduling::DefaultFrameScheduler::kMinPredictedFrameDuration,
           scheduling::DefaultFrameScheduler::kInitialRenderDuration,
           scheduling::DefaultFrameScheduler::kInitialUpdateDuration));
   engine_ = std::make_unique<Engine>(context_provider_.context(), frame_scheduler_,
diff --git a/src/ui/scenic/lib/scheduling/default_frame_scheduler.h b/src/ui/scenic/lib/scheduling/default_frame_scheduler.h
index 399c342..bb05773 100644
--- a/src/ui/scenic/lib/scheduling/default_frame_scheduler.h
+++ b/src/ui/scenic/lib/scheduling/default_frame_scheduler.h
@@ -69,6 +69,7 @@
 
   void ClearCallbacksForSession(SessionId session_id) override;
 
+  constexpr static zx::duration kMinPredictedFrameDuration = zx::msec(0);
   constexpr static zx::duration kInitialRenderDuration = zx::msec(5);
   constexpr static zx::duration kInitialUpdateDuration = zx::msec(1);
 
diff --git a/src/ui/scenic/lib/scheduling/tests/frame_predictor_unittest.cc b/src/ui/scenic/lib/scheduling/tests/frame_predictor_unittest.cc
index 72281c4..657b1c8 100644
--- a/src/ui/scenic/lib/scheduling/tests/frame_predictor_unittest.cc
+++ b/src/ui/scenic/lib/scheduling/tests/frame_predictor_unittest.cc
@@ -24,14 +24,13 @@
  protected:
   // | ::testing::Test |
   void SetUp() override {
-    predictor_ = std::make_unique<WindowedFramePredictor>(kInitialRenderTimePrediction,
-                                                          kInitialUpdateTimePrediction);
+    predictor_ = std::make_unique<WindowedFramePredictor>(
+        kMinPredictedFrameDuration, kInitialRenderTimePrediction, kInitialUpdateTimePrediction);
   }
   // | ::testing::Test |
-  void TearDown() override {
-    predictor_.reset();
-  }
+  void TearDown() override { predictor_.reset(); }
 
+  static constexpr zx::duration kMinPredictedFrameDuration = zx::msec(0);
   static constexpr zx::duration kInitialRenderTimePrediction = zx::msec(4);
   static constexpr zx::duration kInitialUpdateTimePrediction = zx::msec(2);
 
@@ -257,6 +256,63 @@
   EXPECT_LE(prediction.latch_point_time.get(), now.get() + vsync_interval.get());
 }
 
+TEST(WindowedFramePredictorMinFrameDurationTest, BasicPredictions_ShouldRespectMinFrameTime) {
+  zx::duration kMinPredictedFrameDuration = zx::msec(14);
+  zx::duration kInitialRenderTimePrediction = zx::msec(2);
+  zx::duration kInitialUpdateTimePrediction = zx::msec(2);
+  auto predictor = std::make_unique<WindowedFramePredictor>(
+      kMinPredictedFrameDuration, kInitialRenderTimePrediction, kInitialUpdateTimePrediction);
+
+  PredictionRequest request = {.now = ms_to_time(1),
+                               .requested_presentation_time = ms_to_time(16),
+                               .last_vsync_time = ms_to_time(0),
+                               .vsync_interval = zx::msec(16)};
+
+  auto prediction = predictor->GetPrediction(request);
+  EXPECT_EQ(prediction.presentation_time - prediction.latch_point_time, kMinPredictedFrameDuration);
+}
+
+TEST(WindowedFramePredictorMinFrameDurationTest, BasicPredictions_CanPassMinFrameTime) {
+  zx::duration kMinPredictedFrameDuration = zx::msec(5);
+  zx::duration kInitialRenderTimePrediction = zx::msec(3);
+  zx::duration kInitialUpdateTimePrediction = zx::msec(3);
+  auto predictor = std::make_unique<WindowedFramePredictor>(
+      kMinPredictedFrameDuration, kInitialRenderTimePrediction, kInitialUpdateTimePrediction);
+
+  PredictionRequest request = {.now = ms_to_time(1),
+                               .requested_presentation_time = ms_to_time(16),
+                               .last_vsync_time = ms_to_time(0),
+                               .vsync_interval = zx::msec(16)};
+
+  auto prediction = predictor->GetPrediction(request);
+  EXPECT_GT(prediction.presentation_time - prediction.latch_point_time, kMinPredictedFrameDuration);
+}
+
+TEST(WindowedFramePredictorMinFrameDurationTest,
+     PredictionsAfterUpdating_ShouldRespectMinFrameTime) {
+  zx::duration kMinPredictedFrameDuration = zx::msec(13);
+  zx::duration kInitialRenderTimePrediction = zx::msec(2);
+  zx::duration kInitialUpdateTimePrediction = zx::msec(2);
+  auto predictor = std::make_unique<WindowedFramePredictor>(
+      kMinPredictedFrameDuration, kInitialRenderTimePrediction, kInitialUpdateTimePrediction);
+
+  const zx::duration update_duration = zx::msec(3);
+  const zx::duration render_duration = zx::msec(3);
+  const size_t kBiggerThanAllPredictionWindows = 5;
+  for (size_t i = 0; i < kBiggerThanAllPredictionWindows; ++i) {
+    predictor->ReportRenderDuration(render_duration);
+    predictor->ReportUpdateDuration(update_duration);
+  }
+
+  PredictionRequest request = {.now = ms_to_time(1),
+                               .requested_presentation_time = ms_to_time(16),
+                               .last_vsync_time = ms_to_time(0),
+                               .vsync_interval = zx::msec(16)};
+
+  auto prediction = predictor->GetPrediction(request);
+  EXPECT_EQ(prediction.presentation_time - prediction.latch_point_time, kMinPredictedFrameDuration);
+}
+
 // ---------------------------------------------------------------------------
 // ConstantFramePredictor tests
 // ---------------------------------------------------------------------------
diff --git a/src/ui/scenic/lib/scheduling/tests/frame_scheduler_test.cc b/src/ui/scenic/lib/scheduling/tests/frame_scheduler_test.cc
index ca9ef83..8fa3551 100644
--- a/src/ui/scenic/lib/scheduling/tests/frame_scheduler_test.cc
+++ b/src/ui/scenic/lib/scheduling/tests/frame_scheduler_test.cc
@@ -28,7 +28,8 @@
 std::unique_ptr<DefaultFrameScheduler> FrameSchedulerTest::CreateDefaultFrameScheduler() {
   auto scheduler = std::make_unique<DefaultFrameScheduler>(
       vsync_timing_,
-      std::make_unique<WindowedFramePredictor>(DefaultFrameScheduler::kInitialRenderDuration,
+      std::make_unique<WindowedFramePredictor>(DefaultFrameScheduler::kMinPredictedFrameDuration,
+                                               DefaultFrameScheduler::kInitialRenderDuration,
                                                DefaultFrameScheduler::kInitialUpdateDuration));
   scheduler->SetFrameRenderer(mock_renderer_->GetWeakPtr());
   scheduler->AddSessionUpdater(mock_updater_->GetWeakPtr());
diff --git a/src/ui/scenic/lib/scheduling/windowed_frame_predictor.cc b/src/ui/scenic/lib/scheduling/windowed_frame_predictor.cc
index 33c221a..34ec286 100644
--- a/src/ui/scenic/lib/scheduling/windowed_frame_predictor.cc
+++ b/src/ui/scenic/lib/scheduling/windowed_frame_predictor.cc
@@ -10,9 +10,11 @@
 
 namespace scheduling {
 
-WindowedFramePredictor::WindowedFramePredictor(zx::duration initial_render_duration_prediction,
+WindowedFramePredictor::WindowedFramePredictor(zx::duration min_predicted_frame_duration,
+                                               zx::duration initial_render_duration_prediction,
                                                zx::duration initial_update_duration_prediction)
-    : render_duration_predictor_(kRenderPredictionWindowSize, initial_render_duration_prediction),
+    : min_predicted_frame_duration_(min_predicted_frame_duration),
+      render_duration_predictor_(kRenderPredictionWindowSize, initial_render_duration_prediction),
       update_duration_predictor_(kUpdatePredictionWindowSize, initial_update_duration_prediction) {}
 
 WindowedFramePredictor::~WindowedFramePredictor() {}
@@ -31,8 +33,10 @@
   const zx::duration predicted_time_to_update = update_duration_predictor_.GetPrediction();
   const zx::duration predicted_time_to_render = render_duration_predictor_.GetPrediction();
 
-  const zx::duration predicted_frame_duration = std::min(
-      kMaxFrameTime, predicted_time_to_update + predicted_time_to_render + kHardcodedMargin);
+  const zx::duration predicted_frame_duration =
+      std::max(min_predicted_frame_duration_,
+               std::min(kMaxPredictedFrameDuration,
+                        predicted_time_to_update + predicted_time_to_render + kHardcodedMargin));
 
   // Pretty print the times in milliseconds.
   TRACE_INSTANT("gfx", "WindowedFramePredictor::GetPrediction", TRACE_SCOPE_PROCESS,
diff --git a/src/ui/scenic/lib/scheduling/windowed_frame_predictor.h b/src/ui/scenic/lib/scheduling/windowed_frame_predictor.h
index 85453cb..7c19005 100644
--- a/src/ui/scenic/lib/scheduling/windowed_frame_predictor.h
+++ b/src/ui/scenic/lib/scheduling/windowed_frame_predictor.h
@@ -13,7 +13,8 @@
 
 class WindowedFramePredictor : public FramePredictor {
  public:
-  WindowedFramePredictor(zx::duration initial_render_duration_prediction,
+  WindowedFramePredictor(zx::duration min_predicted_frame_duration,
+                         zx::duration initial_render_duration_prediction,
                          zx::duration initial_update_duration_prediction);
   ~WindowedFramePredictor();
 
@@ -39,7 +40,11 @@
   // Rarely, it is possible for abnormally long GPU contexts to occur, and
   // when they occur we do not want them to mess up future predictions by
   // too much. We therefore clamp RenderDurations by this much.
-  const zx::duration kMaxFrameTime = zx::usec(16'667);  // 16.667ms
+  const zx::duration kMaxPredictedFrameDuration = zx::usec(16'667);  // 16.667ms
+
+  // Lower bound for frame time prediction. It is useful when we want to set a fixed offset for
+  // certain cases. It can be set specifically for the board via config.
+  const zx::duration min_predicted_frame_duration_;
 
   // Render time prediction.
   const size_t kRenderPredictionWindowSize = 3;