diff --git a/garnet/lib/ui/gfx/BUILD.gn b/garnet/lib/ui/gfx/BUILD.gn index b3a13bde76e42a2d9768722c59e882f19dab1dbf..978e572f2f37a7b7fd6e350e0d05090a19374c68 100644 --- a/garnet/lib/ui/gfx/BUILD.gn +++ b/garnet/lib/ui/gfx/BUILD.gn @@ -254,9 +254,13 @@ source_set("display") { source_set("frame_scheduler") { sources = [ # TODO(SCN-1398): Move files out of engine/. + "engine/duration_predictor.cc", + "engine/duration_predictor.h", "engine/default_frame_scheduler.cc", "engine/default_frame_scheduler.h", "engine/frame_scheduler.h", + "engine/frame_predictor.cc", + "engine/frame_predictor.h", "engine/frame_timings.cc", "engine/frame_timings.h", diff --git a/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc b/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc index b7d20558a6e7f869fd159ed9155575e16da18fec..7b083e570beb4a15a00c2896ab671559126d88ff 100644 --- a/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc +++ b/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc @@ -199,8 +199,9 @@ void DefaultFrameScheduler::MaybeRenderFrame(async_dispatcher_t*, // calls until this point are applied to the next Scenic frame. delegate_.session_updater->RatchetPresentCallbacks(); - auto frame_timings = - fxl::MakeRefCounted<FrameTimings>(this, frame_number_, presentation_time); + const zx_time_t frame_render_start_time = async_now(dispatcher_); + auto frame_timings = fxl::MakeRefCounted<FrameTimings>( + this, frame_number_, presentation_time, frame_render_start_time); inspect_frame_number_.Set(frame_number_); // Render the frame. diff --git a/garnet/lib/ui/gfx/engine/duration_predictor.cc b/garnet/lib/ui/gfx/engine/duration_predictor.cc new file mode 100644 index 0000000000000000000000000000000000000000..c474d527c81193ba90853aff5b430b8171d4041f --- /dev/null +++ b/garnet/lib/ui/gfx/engine/duration_predictor.cc @@ -0,0 +1,45 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "garnet/lib/ui/gfx/engine/duration_predictor.h" + +#include <src/lib/fxl/logging.h> + +namespace scenic_impl { +namespace gfx { + +DurationPredictor::DurationPredictor(size_t optimism_window_size, + zx::duration initial_prediction) + : kWindowSize(optimism_window_size), + window_(kWindowSize, initial_prediction) { + FXL_DCHECK(kWindowSize > 0); + current_minimum_duration_index_ = kWindowSize - 1; +} + +zx::duration DurationPredictor::GetPrediction() const { + return window_[current_minimum_duration_index_]; +} + +void DurationPredictor::InsertNewMeasurement(zx::duration duration) { + // Move window forward. + window_.push_front(duration); + window_.pop_back(); + ++current_minimum_duration_index_; + + if (current_minimum_duration_index_ >= kWindowSize) { + // If old min went out of scope, find the new min. + current_minimum_duration_index_ = 0; + for (size_t i = 1; i < kWindowSize; ++i) { + if (window_[i] < window_[current_minimum_duration_index_]) { + current_minimum_duration_index_ = i; + } + } + } else if (window_.front() <= window_[current_minimum_duration_index_]) { + // Use newest possible minimum. + current_minimum_duration_index_ = 0; + } +} + +} // namespace gfx +} // namespace scenic_impl diff --git a/garnet/lib/ui/gfx/engine/duration_predictor.h b/garnet/lib/ui/gfx/engine/duration_predictor.h new file mode 100644 index 0000000000000000000000000000000000000000..e6111fcd5d4a705336d1defca776b276346f0055 --- /dev/null +++ b/garnet/lib/ui/gfx/engine/duration_predictor.h @@ -0,0 +1,44 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GARNET_LIB_UI_GFX_ENGINE_DURATION_PREDICTOR_H_ +#define GARNET_LIB_UI_GFX_ENGINE_DURATION_PREDICTOR_H_ + +#include <lib/zx/time.h> + +#include <deque> + +namespace scenic_impl { +namespace gfx { + +// Class for predicting future durations based on previous measurements. Uses an +// optimistic approach that determines the "most optimistic duration" based on +// the last N measurements, where N is a range of values set by the client. +// +// TODO(SCN-1415) When Scenic has priority gpu vk queues, revisit this +// prediction strategy. Scenic currently cannot report accurate GPU duration +// measurements because it currently has no way to preempt work on the GPU. +// This causes render durations to be very noisy and not representative of the +// work Scenic is doing. +class DurationPredictor { + public: + DurationPredictor(size_t optimism_window_size, + zx::duration initial_prediction); + ~DurationPredictor() = default; + + zx::duration GetPrediction() const; + + void InsertNewMeasurement(zx::duration duration); + + private: + const size_t kWindowSize; + std::deque<zx::duration> window_; // Ring buffer. + + size_t current_minimum_duration_index_ = 0; +}; + +} // namespace gfx +} // namespace scenic_impl + +#endif // GARNET_LIB_UI_GFX_ENGINE_DURATION_PREDICTOR_H_ diff --git a/garnet/lib/ui/gfx/engine/frame_predictor.cc b/garnet/lib/ui/gfx/engine/frame_predictor.cc new file mode 100644 index 0000000000000000000000000000000000000000..dcbc100ffb00044eef12b650857d988497dc341e --- /dev/null +++ b/garnet/lib/ui/gfx/engine/frame_predictor.cc @@ -0,0 +1,102 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "garnet/lib/ui/gfx/engine/frame_predictor.h" + +#include <src/lib/fxl/logging.h> +#include <trace/event.h> + +#include <algorithm> + +namespace scenic_impl { +namespace gfx { + +FramePredictor::FramePredictor(zx::duration initial_render_duration_prediction, + zx::duration initial_update_duration_prediction) + : render_duration_predictor_(kRenderPredictionWindowSize, + initial_render_duration_prediction), + update_duration_predictor_(kUpdatePredictionWindowSize, + initial_update_duration_prediction) {} + +void FramePredictor::ReportRenderDuration(zx::duration time_to_render) { + FXL_DCHECK(time_to_render >= zx::duration(0)); + render_duration_predictor_.InsertNewMeasurement(time_to_render); +} + +void FramePredictor::ReportUpdateDuration(zx::duration time_to_update) { + FXL_DCHECK(time_to_update >= zx::duration(0)); + update_duration_predictor_.InsertNewMeasurement(time_to_update); +} + +zx::duration FramePredictor::PredictTotalRequiredDuration() const { + 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 = + predicted_time_to_update + predicted_time_to_render + kHardcodedMargin; + + TRACE_INSTANT("gfx", "FramePredictor::PredictRequiredFrameRenderTime", + TRACE_SCOPE_THREAD, "Predicted frame duration", + predicted_frame_duration.get()); + + return predicted_frame_duration; +} + +// static +zx::time FramePredictor::ComputeNextSyncTime(zx::time last_sync_time, + zx::duration sync_interval, + zx::time min_sync_time) { + // If the last sync time is greater than or equal to the minimum acceptable + // sync time, just return the last sync. + // Note: in practice, these numbers will likely differ. The "equal to" + // comparison is necessary for tests, which have much tighter control on time. + if (last_sync_time >= min_sync_time) { + return last_sync_time; + } + + const int64_t num_intervals = (min_sync_time - last_sync_time) / sync_interval; + return last_sync_time + (sync_interval * (num_intervals + 1)); +} + +PredictedTimes FramePredictor::GetPrediction(PredictionRequest request) const { +#if SCENIC_IGNORE_VSYNC + // Predict that the frame should be rendered immediately. + return {.presentation_time = request.now, .latch_point_time = request.now}; +#endif + + const zx::duration required_frame_duration = PredictTotalRequiredDuration(); + + // Calculate minimum time this would sync to. It is last vsync time plus half + // a vsync-interval (to allow for jitter for the VSYNC signal), or the current + // time plus the expected render time, whichever is larger, so we know we have + // enough time to render for that sync. + zx::time min_sync_time = + std::max((request.last_vsync_time + (request.vsync_interval / 2)), + (request.now + required_frame_duration)); + const zx::time target_vsync_time = ComputeNextSyncTime( + request.last_vsync_time, request.vsync_interval, min_sync_time); + + // Ensure the requested presentation time is current. + zx::time target_presentation_time = + request.requested_presentation_time < request.now + ? request.now + : request.requested_presentation_time; + // Compute the next presentation time from the target vsync time (inclusive), + // that is at least the current requested present time. + target_presentation_time = ComputeNextSyncTime( + target_vsync_time, request.vsync_interval, target_presentation_time); + + // Find time the client should latch and start rendering in order to + // frame in time for the target present. + zx::time latch_point = target_presentation_time - required_frame_duration; + + return {.presentation_time = target_presentation_time, + .latch_point_time = latch_point}; + +} + +} // namespace gfx +} // namespace scenic_impl diff --git a/garnet/lib/ui/gfx/engine/frame_predictor.h b/garnet/lib/ui/gfx/engine/frame_predictor.h new file mode 100644 index 0000000000000000000000000000000000000000..cbbb6e840a84b7348a603b4e371a141ac7781e4a --- /dev/null +++ b/garnet/lib/ui/gfx/engine/frame_predictor.h @@ -0,0 +1,91 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GARNET_LIB_UI_GFX_ENGINE_FRAME_PREDICTOR_H_ +#define GARNET_LIB_UI_GFX_ENGINE_FRAME_PREDICTOR_H_ + +#include <lib/zx/time.h> + +#include <vector> + +#include "garnet/lib/ui/gfx/engine/duration_predictor.h" + +namespace scenic_impl { +namespace gfx { + +struct PredictedTimes { + // The point at which a client should begin an update and render a frame, + // so that it is done by the |presentation_time|. + zx::time latch_point_time; + // The predicted presentation time. This corresponds to a future VSYNC. + zx::time presentation_time; +}; + +struct PredictionRequest { + zx::time now; + // The minimum presentation time a client would like to hit. + zx::time requested_presentation_time; + zx::time last_vsync_time; + zx::duration vsync_interval; +}; + +// Predicts viable presentation times and corresponding latch-points for a +// frame, based on previously reported update and render durations. +class FramePredictor { + public: + FramePredictor(zx::duration initial_render_duration_prediction, + zx::duration initial_update_duration_prediction); + ~FramePredictor() = default; + + // Computes the target presentation time for + // |request.requested_presentation_time|, and a latch-point that is early + // enough to apply one update and render a frame, in order to hit the + // predicted presentation time. + // + // Both |PredictedTimes.latch_point_time| and |PredictedTimes.presentation_time| + // are guaranteed to be after |request.now|. + // |PredictedTimes.presentation_time| is guaranteed to be later than or equal + // to |request.requested_presentation_time|. + PredictedTimes GetPrediction(PredictionRequest request) const; + + // Used by the client to report a measured render duration. The render + // duration is the CPU + GPU time it takes to build and render a frame. This + // will be considered in subsequent calls to |GetPrediction|. + void ReportRenderDuration(zx::duration time_to_render); + + // Used by the client to report a measured update duration. The update + // duration is the time it takes to apply a batch of updates. This will be + // considered in subsequent calls to |GetPrediction|. + void ReportUpdateDuration(zx::duration time_to_update); + + private: + // Returns the next time to synchronize to. + // |last_sync_time| The last known good sync time. + // |sync_interval| The expected time between syncs. + // |min_sync_time| The minimum time allowed to return. + static zx::time ComputeNextSyncTime(zx::time last_sync_time, + zx::duration sync_interval, + zx::time min_sync_time); + // Returns a prediction for how long in total the next frame will take to + // update and render. + zx::duration PredictTotalRequiredDuration() const; + + // Safety margin added to prediction time to reduce impact of noise and + // misprediction. Unfortunately means minimum possible latency is increased + // by the same amount. + const zx::duration kHardcodedMargin = zx::usec(500); // 0.5ms + + // Render time prediction. + const size_t kRenderPredictionWindowSize = 3; + DurationPredictor render_duration_predictor_; + + // Update time prediction. + const size_t kUpdatePredictionWindowSize = 1; + DurationPredictor update_duration_predictor_; +}; + +} // namespace gfx +} // namespace scenic_impl + +#endif // GARNET_LIB_UI_GFX_ENGINE_FRAME_PREDICTOR_H_ diff --git a/garnet/lib/ui/gfx/engine/frame_timings.cc b/garnet/lib/ui/gfx/engine/frame_timings.cc index 5aefce662ff580775351ee2abcb5385084daa1d9..49cffbad61aaad02a5dd27b4c11068b224338a9d 100644 --- a/garnet/lib/ui/gfx/engine/frame_timings.cc +++ b/garnet/lib/ui/gfx/engine/frame_timings.cc @@ -9,14 +9,14 @@ namespace scenic_impl { namespace gfx { -FrameTimings::FrameTimings() : FrameTimings(nullptr, 0, 0) {} - FrameTimings::FrameTimings(FrameScheduler* frame_scheduler, uint64_t frame_number, - zx_time_t target_presentation_time) + zx_time_t target_presentation_time, + zx_time_t rendering_started_time) : frame_scheduler_(frame_scheduler), frame_number_(frame_number), - target_presentation_time_(target_presentation_time) {} + target_presentation_time_(target_presentation_time), + rendering_started_time_(rendering_started_time) {} size_t FrameTimings::AddSwapchain(Swapchain* swapchain) { // All swapchains that we are timing must be added before any of them finish. diff --git a/garnet/lib/ui/gfx/engine/frame_timings.h b/garnet/lib/ui/gfx/engine/frame_timings.h index d425cd601bd59286a0a244451dc4417b363f2a55..3180c0a0eced5548b11ae1630fa90ba4fa1fb6a1 100644 --- a/garnet/lib/ui/gfx/engine/frame_timings.h +++ b/garnet/lib/ui/gfx/engine/frame_timings.h @@ -6,6 +6,7 @@ #define GARNET_LIB_UI_GFX_ENGINE_FRAME_TIMINGS_H_ #include <lib/zx/time.h> + #include <vector> #include "src/ui/lib/escher/base/reffable.h" @@ -24,9 +25,9 @@ using FrameTimingsPtr = fxl::RefPtr<FrameTimings>; // FrameScheduler is notified via OnFramePresented(). class FrameTimings : public escher::Reffable { public: - FrameTimings(); FrameTimings(FrameScheduler* frame_scheduler, uint64_t frame_number, - zx_time_t target_presentation_time); + zx_time_t target_presentation_time, + zx_time_t rendering_started_time); // Add a swapchain that is used as a render target this frame. Return an // index that can be used to indicate when rendering for that swapchain is @@ -61,6 +62,8 @@ class FrameTimings : public escher::Reffable { return actual_presentation_time_; } + zx_time_t rendering_started_time() const { return rendering_started_time_; } + zx_time_t rendering_finished_time() const { return rendering_finished_time_; } private: @@ -85,6 +88,7 @@ class FrameTimings : public escher::Reffable { FrameScheduler* const frame_scheduler_; const uint64_t frame_number_; const zx_time_t target_presentation_time_; + const zx_time_t rendering_started_time_; zx_time_t actual_presentation_time_ = 0; zx_time_t rendering_finished_time_ = 0; size_t frame_rendered_count_ = 0; diff --git a/garnet/lib/ui/gfx/tests/BUILD.gn b/garnet/lib/ui/gfx/tests/BUILD.gn index 5f2dc5c59f9a737520c0f7fbc1a86975fc6d229d..1ba496754cf8b5f0ad016597dbe9717af2550353 100644 --- a/garnet/lib/ui/gfx/tests/BUILD.gn +++ b/garnet/lib/ui/gfx/tests/BUILD.gn @@ -76,8 +76,10 @@ executable("unittests") { sources = [ "compositor_unittest.cc", "default_frame_scheduler_unittest.cc", + "duration_predictor_unittest.cc", "escher_vulkan_smoke_test.cc", "event_timestamper_unittest.cc", + "frame_predictor_unittest.cc", "frame_timings_unittest.cc", "gfx_command_applier_unittest.cc", "hardware_layer_assignment_unittest.cc", diff --git a/garnet/lib/ui/gfx/tests/duration_predictor_unittest.cc b/garnet/lib/ui/gfx/tests/duration_predictor_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..0f71602e16812395ef4d4edb240d44025f417527 --- /dev/null +++ b/garnet/lib/ui/gfx/tests/duration_predictor_unittest.cc @@ -0,0 +1,90 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "garnet/lib/ui/gfx/engine/duration_predictor.h" + +#include <lib/gtest/test_loop_fixture.h> + +namespace scenic_impl { +namespace gfx { +namespace test { + +TEST(DurationPredictor, FirstPredictionIsInitialPrediction) { + const size_t kWindowSize = 4; + const zx::duration kInitialPrediction = zx::usec(500); + DurationPredictor predictor(kWindowSize, kInitialPrediction); + EXPECT_EQ(predictor.GetPrediction(), kInitialPrediction); +} + +TEST(DurationPredictor, PredictionAfterWindowFlushIsMeasurement) { + const size_t kWindowSize = 4; + const zx::duration kInitialPrediction = zx::msec(1); + DurationPredictor predictor(kWindowSize, kInitialPrediction); + + const zx::duration measurement = zx::msec(5); + EXPECT_GT(measurement, kInitialPrediction); + predictor.InsertNewMeasurement(measurement); + EXPECT_EQ(predictor.GetPrediction(), kInitialPrediction); + + for (size_t i = 0; i < kWindowSize - 1; ++i) { + predictor.InsertNewMeasurement(measurement); + } + EXPECT_EQ(predictor.GetPrediction(), measurement); +} + +TEST(DurationPredictor, PredictionIsSmallestInWindowAsMeasurementsIncrease) { + size_t window_size = 10; + DurationPredictor predictor(window_size, /* initial prediction */ zx::usec(0)); + + for (size_t i = 1; i <= window_size; ++i) { + predictor.InsertNewMeasurement(zx::msec(i)); + } + EXPECT_EQ(predictor.GetPrediction(), zx::msec(1)); +} + +TEST(DurationPredictor, PredictionIsSmallestInWindowAsMeasurementsDecrease) { + size_t window_size = 10; + DurationPredictor predictor(window_size, /* initial prediction */ zx::usec(0)); + + for (size_t i = window_size; i > 0; --i) { + predictor.InsertNewMeasurement(zx::msec(i)); + } + EXPECT_EQ(predictor.GetPrediction(), zx::msec(1)); +} + +TEST(DurationPredictor, PredictionIsSmallestInWindow) { + size_t window_size = 10; + DurationPredictor predictor(window_size, /* initial prediction */ zx::usec(0)); + + const std::vector<zx_duration_t> measurements{12, 4, 5, 2, 8, 55, 13, 6, 8, 9}; + for (size_t i = 0; i < measurements.size(); ++i) { + predictor.InsertNewMeasurement(zx::msec(measurements[i])); + } + EXPECT_EQ(predictor.GetPrediction(), zx::msec(2)); +} + +TEST(DurationPredictor, MinIsResetWhenSmallestIsOutOfWindow) { + size_t window_size = 4; + DurationPredictor predictor(window_size, /* initial prediction */ zx::usec(0)); + + const std::vector<zx_duration_t> measurements{12, 4, 5, 2, 8, 55, 13, 6, 8, 9}; + for (size_t i = 0; i < measurements.size(); ++i) { + predictor.InsertNewMeasurement(zx::msec(measurements[i])); + } + EXPECT_EQ(predictor.GetPrediction(), zx::msec(6)); +} + +TEST(DurationPredictor, WindowSizeOfOneWorks) { + size_t window_size = 1; + DurationPredictor predictor(window_size, /* initial prediction */ zx::usec(0)); + + for (size_t i = 0; i < 5; ++i) { + predictor.InsertNewMeasurement(zx::msec(i)); + } + EXPECT_EQ(predictor.GetPrediction(), zx::msec(4)); +} + +} // namespace test +} // namespace gfx +} // namespace scenic_impl diff --git a/garnet/lib/ui/gfx/tests/frame_predictor_unittest.cc b/garnet/lib/ui/gfx/tests/frame_predictor_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..6ca6a6bfb3a90ff0bfd52fde6a2ff6dd1ba330cc --- /dev/null +++ b/garnet/lib/ui/gfx/tests/frame_predictor_unittest.cc @@ -0,0 +1,227 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "garnet/lib/ui/gfx/engine/frame_predictor.h" + +#include <lib/gtest/test_loop_fixture.h> + +#include "garnet/lib/ui/gfx/tests/error_reporting_test.h" + +namespace scenic_impl { +namespace gfx { +namespace test { + +class FramePredictorTest : public ErrorReportingTest { + protected: + // | ::testing::Test | + void SetUp() override { + predictor_ = std::make_unique<FramePredictor>(kInitialRenderTimePrediction, + kInitialUpdateTimePrediction); + } + // | ::testing::Test | + void TearDown() override { predictor_.reset(); } + + zx::time ms_to_time(uint64_t ms) { + return zx::time(0) + zx::msec(ms); + } + + static constexpr zx::duration kInitialRenderTimePrediction = + zx::msec(4); + static constexpr zx::duration kInitialUpdateTimePrediction = + zx::msec(2); + + std::unique_ptr<FramePredictor> predictor_; +}; + +TEST_F(FramePredictorTest, BasicPredictions_ShouldBeReasonable) { + PredictionRequest request = { + .now = ms_to_time(5), + .requested_presentation_time = ms_to_time(10), + .last_vsync_time = ms_to_time(0), + .vsync_interval = zx::msec(10)}; + + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GT(prediction.presentation_time, request.now); + EXPECT_GE(prediction.latch_point_time, request.now); + EXPECT_LT(prediction.latch_point_time, prediction.presentation_time); +} + +TEST_F(FramePredictorTest, PredictionsAfterUpdating_ShouldBeMoreReasonable) { + const zx::duration update_duration = zx::msec(2); + const zx::duration render_duration = zx::msec(5); + + 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(5), + .requested_presentation_time = ms_to_time(0), + .last_vsync_time = ms_to_time(0), + .vsync_interval = zx::msec(10)}; + + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GT(prediction.presentation_time, request.now); + EXPECT_GE(prediction.latch_point_time, request.now); + + EXPECT_GE(prediction.presentation_time - prediction.latch_point_time, + update_duration + render_duration); +} + +TEST_F(FramePredictorTest, + OneExpensiveTime_ShouldNotPredictForFutureVsyncIntervals) { + const zx::duration update_duration = zx::msec(4); + const zx::duration render_duration = zx::msec(10); + const zx::duration vsync_interval = zx::msec(10); + + predictor_->ReportRenderDuration(render_duration); + predictor_->ReportUpdateDuration(update_duration); + + PredictionRequest request = {.now = ms_to_time(0), + .requested_presentation_time = ms_to_time(0), + .last_vsync_time = ms_to_time(0), + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GE(prediction.latch_point_time, request.now); + EXPECT_LE(prediction.presentation_time, + request.last_vsync_time + request.vsync_interval); +} + +TEST_F(FramePredictorTest, + ManyExpensiveTimes_ShouldPredictForFutureVsyncIntervals) { + const zx::duration update_duration = zx::msec(4); + const zx::duration render_duration = zx::msec(10); + const zx::duration vsync_interval = zx::msec(10); + + for (size_t i = 0; i < 10; i++) { + predictor_->ReportRenderDuration(render_duration); + predictor_->ReportUpdateDuration(update_duration); + } + + PredictionRequest request = {.now = ms_to_time(3), + .requested_presentation_time = ms_to_time(0), + .last_vsync_time = ms_to_time(0), + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GE(prediction.latch_point_time, request.now); + EXPECT_GE(prediction.presentation_time, + request.last_vsync_time + request.vsync_interval); + EXPECT_LE(prediction.presentation_time, + request.last_vsync_time + request.vsync_interval * 2); + EXPECT_LE(prediction.latch_point_time, + prediction.presentation_time - request.vsync_interval); +} + +TEST_F(FramePredictorTest, ManyFramesOfPredictions_ShouldBeReasonable) { + const zx::duration vsync_interval = zx::msec(10); + + zx::time now = ms_to_time(0); + zx::time requested_present = ms_to_time(8); + zx::time last_vsync_time = ms_to_time(0); + for (uint64_t i = 0; i < 50; ++i) { + zx::duration update_duration = zx::msec(i % 5); + zx::duration render_duration = zx::msec(5); + predictor_->ReportUpdateDuration(update_duration); + predictor_->ReportRenderDuration(render_duration); + EXPECT_GE(vsync_interval, update_duration + render_duration); + + PredictionRequest request = {.now = now, + .requested_presentation_time = requested_present, + .last_vsync_time = last_vsync_time, + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GE(prediction.latch_point_time, request.now); + EXPECT_GE(prediction.presentation_time, requested_present); + EXPECT_LE(prediction.presentation_time, + requested_present + vsync_interval * 2); + + // For the next frame, increase time to be after the predicted present to + // emulate a client that is regularly scheduling frames. + now = prediction.presentation_time + zx::msec(1); + requested_present = prediction.presentation_time + vsync_interval; + last_vsync_time = prediction.presentation_time; + } +} + +TEST_F(FramePredictorTest, MissedLastVsync_ShouldPredictWithInterval) { + const zx::duration update_duration = zx::msec(4); + const zx::duration render_duration = zx::msec(5); + predictor_->ReportRenderDuration(render_duration); + predictor_->ReportUpdateDuration(update_duration); + + const zx::duration vsync_interval = zx::msec(16); + zx::time last_vsync_time = ms_to_time(16); + // Make now be more than a vsync_interval beyond the last_vsync_time + zx::time now = last_vsync_time + (vsync_interval * 2) + zx::msec(3); + zx::time requested_present = now + zx::msec(9); + PredictionRequest request = {.now = now, + .requested_presentation_time = requested_present, + .last_vsync_time = last_vsync_time, + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + // The predicted presentation and wakeup times should be greater than one + // vsync interval since the last reported vsync time. + EXPECT_GE(prediction.presentation_time, last_vsync_time + vsync_interval); + EXPECT_LE(prediction.presentation_time, now + (request.vsync_interval * 2)); + EXPECT_LE(prediction.presentation_time - prediction.latch_point_time, + vsync_interval); +} + +TEST_F(FramePredictorTest, MissedPresentRequest_ShouldTargetNextVsync) { + const zx::duration update_duration = zx::msec(2); + const zx::duration render_duration = zx::msec(4); + predictor_->ReportRenderDuration(render_duration); + predictor_->ReportUpdateDuration(update_duration); + + const zx::duration vsync_interval = zx::msec(10); + zx::time last_vsync_time = ms_to_time(10); + zx::time now = ms_to_time(12); + // Request a present time in the past. + zx::time requested_present = now - zx::msec(1); + PredictionRequest request = {.now = now, + .requested_presentation_time = requested_present, + .last_vsync_time = last_vsync_time, + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + EXPECT_GE(prediction.presentation_time, last_vsync_time + vsync_interval); + EXPECT_LE(prediction.presentation_time, last_vsync_time + (vsync_interval * 2)); + EXPECT_GE(prediction.latch_point_time, + prediction.presentation_time - vsync_interval); +} + +TEST_F(FramePredictorTest, AttemptsToBeLowLatent_ShouldBePossible) { + const zx::duration update_duration = zx::msec(1); + const zx::duration render_duration = zx::msec(3); + predictor_->ReportRenderDuration(render_duration); + predictor_->ReportUpdateDuration(update_duration); + + const zx::duration vsync_interval = zx::msec(10); + zx::time last_vsync_time = ms_to_time(10); + zx::time requested_present = last_vsync_time + vsync_interval; + zx::time now = + requested_present - update_duration - render_duration - zx::msec(1); + EXPECT_GT(now, last_vsync_time); + + PredictionRequest request = {.now = now, + .requested_presentation_time = requested_present, + .last_vsync_time = last_vsync_time, + .vsync_interval = vsync_interval}; + auto prediction = predictor_->GetPrediction(request); + + // The prediction should be for the next vsync. + EXPECT_LE(prediction.presentation_time, last_vsync_time + vsync_interval); + EXPECT_GE(prediction.latch_point_time, now); +} + +} // namespace test +} // namespace gfx +} // namespace scenic_impl diff --git a/garnet/lib/ui/gfx/tests/frame_timings_unittest.cc b/garnet/lib/ui/gfx/tests/frame_timings_unittest.cc index 717ff5d030e4673cbd5c0f905f8fb22b32d70cc5..4086a8f1573971516b421a03b93d496bfacafcda 100644 --- a/garnet/lib/ui/gfx/tests/frame_timings_unittest.cc +++ b/garnet/lib/ui/gfx/tests/frame_timings_unittest.cc @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "garnet/lib/ui/gfx/tests/error_reporting_test.h" - #include "garnet/lib/ui/gfx/engine/frame_timings.h" + +#include "garnet/lib/ui/gfx/tests/error_reporting_test.h" #include "garnet/lib/ui/gfx/tests/frame_scheduler_mocks.h" namespace scenic_impl { @@ -19,7 +19,8 @@ class FrameTimingsTest : public ErrorReportingTest { frame_timings_ = fxl::MakeRefCounted<FrameTimings>(frame_scheduler_.get(), /* frame number */ 1, - /* target presentation time*/ 1); + /* target presentation time*/ 1, + /* render started time */ 0); frame_timings_->AddSwapchain(nullptr); } void TearDown() override { frame_scheduler_.reset(); }