[scenic] Collect all waiting frames into a single instance
Make sure we only have a single async wait happening at a time in DefaultFrameScheduler.
This prevents the bug (SCN-1306) where a delayed frame can cause a second frame to
start rendering at the same time. It also makes it easier to reason about scheduling.
SCN-1306 #done
Change-Id: I9bc0fb4f077db9f6c7f346669785b744fc3113d7
diff --git a/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc b/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc
index aa477c8..ffbcb78 100644
--- a/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc
+++ b/garnet/lib/ui/gfx/engine/default_frame_scheduler.cc
@@ -17,17 +17,7 @@
namespace scenic_impl {
namespace gfx {
-namespace {
-
-// Generates a unique ID, used for flow trace events.
-static uint64_t NextFlowId() {
- static std::atomic<uint64_t> counter(0);
- return ++counter;
-}
-
-} // anonymous namespace
-
-DefaultFrameScheduler::DefaultFrameScheduler(Display* const display)
+DefaultFrameScheduler::DefaultFrameScheduler(const Display* display)
: dispatcher_(async_get_default_dispatcher()),
display_(display),
weak_factory_(this) {
@@ -41,26 +31,16 @@
TRACE_SCOPE_PROCESS, "Timestamp",
timings.rendering_finished_time(), "Frame number",
timings.frame_number());
-}
-
-void DefaultFrameScheduler::RequestFrame(zx_time_t presentation_time) {
- if (frame_number_ < 5) {
- FXL_LOG(INFO) << "DefaultFrameScheduler::RequestFrame presentation_time="
- << presentation_time;
- }
- const bool should_schedule_frame =
- requested_presentation_times_.empty() ||
- requested_presentation_times_.top() > presentation_time;
- requested_presentation_times_.push(presentation_time);
- if (should_schedule_frame) {
- ScheduleFrame();
+ currently_rendering_ = false;
+ if (render_pending_) {
+ RequestFrame();
}
}
void DefaultFrameScheduler::SetRenderContinuously(bool render_continuously) {
render_continuously_ = render_continuously;
if (render_continuously_) {
- RequestFrame(0);
+ RequestFrame();
}
}
@@ -73,14 +53,7 @@
}
std::pair<zx_time_t, zx_time_t>
-DefaultFrameScheduler::ComputeNextPresentationAndWakeupTimes() const {
- FXL_DCHECK(!requested_presentation_times_.empty());
- return ComputeTargetPresentationAndWakeupTimes(
- requested_presentation_times_.top());
-}
-
-std::pair<zx_time_t, zx_time_t>
-DefaultFrameScheduler::ComputeTargetPresentationAndWakeupTimes(
+DefaultFrameScheduler::ComputePresentationAndWakeupTimesForTargetTime(
const zx_time_t requested_presentation_time) const {
const zx_time_t last_vsync_time = display_->GetLastVsyncTime();
const zx_duration_t vsync_interval = display_->GetVsyncInterval();
@@ -161,64 +134,51 @@
#endif
}
-void DefaultFrameScheduler::ScheduleFrame() {
- FXL_DCHECK(!requested_presentation_times_.empty());
+void DefaultFrameScheduler::RequestFrame() {
+ FXL_CHECK(!updatable_sessions_.empty() || render_continuously_ ||
+ render_pending_);
+
+ // Logging the first few frames to find common startup bugs.
if (frame_number_ < 5) {
- FXL_LOG(INFO) << "DefaultFrameScheduler::ScheduleFrame";
+ FXL_LOG(INFO) << "DefaultFrameScheduler::RequestFrame";
}
- auto times = ComputeNextPresentationAndWakeupTimes();
- zx_time_t presentation_time = times.first;
- zx_time_t wakeup_time = times.second;
+ auto requested_presentation_time =
+ render_continuously_ || render_pending_
+ ? 0
+ : updatable_sessions_.top().requested_presentation_time;
- TRACE_DURATION("gfx", "FrameScheduler::ScheduleFrame",
- "next requested present time",
- requested_presentation_times_.top(), "presentation time",
- presentation_time, "wakeup_time", wakeup_time);
+ auto next_times = ComputePresentationAndWakeupTimesForTargetTime(
+ requested_presentation_time);
+ auto new_presentation_time = next_times.first;
+ auto new_wakeup_time = next_times.second;
- uint64_t flow_id = NextFlowId();
- TRACE_FLOW_BEGIN("gfx", "FrameScheduler_Request", flow_id);
+ // If there is no render waiting we should schedule a frame.
+ // Likewise, if newly predicted wake up time is earlier than the current one
+ // then we need to reschedule the next wake up.
+ if (!frame_render_task_.is_pending() || new_wakeup_time < wakeup_time_) {
+ frame_render_task_.Cancel();
- async::PostTaskForTime(
- dispatcher_,
- [weak = weak_factory_.GetWeakPtr(), presentation_time, wakeup_time,
- flow_id] {
- if (weak)
- weak->MaybeRenderFrame(presentation_time, wakeup_time, flow_id);
- },
- zx::time(0) + zx::nsec(wakeup_time));
+ wakeup_time_ = new_wakeup_time;
+ next_presentation_time_ = new_presentation_time;
+ frame_render_task_.PostForTime(dispatcher_, zx::time(wakeup_time_));
+ }
}
-void DefaultFrameScheduler::MaybeRenderFrame(zx_time_t presentation_time,
- zx_time_t wakeup_time,
- uint64_t flow_id) {
+void DefaultFrameScheduler::MaybeRenderFrame(async_dispatcher_t*,
+ async::TaskBase*, zx_status_t) {
+ auto presentation_time = next_presentation_time_;
TRACE_DURATION("gfx", "FrameScheduler::MaybeRenderFrame", "presentation_time",
presentation_time);
- TRACE_FLOW_END("gfx", "FrameScheduler_Request", flow_id);
+
+ // Logging the first few frames to find common startup bugs.
if (frame_number_ < 5) {
FXL_LOG(INFO)
<< "DefaultFrameScheduler::MaybeRenderFrame presentation_time="
- << presentation_time << " wakeup_time=" << wakeup_time
+ << presentation_time << " wakeup_time=" << wakeup_time_
<< " frame_number=" << frame_number_;
}
- if (requested_presentation_times_.empty()) {
- // No frame was requested, so none needs to be rendered. More precisely, a
- // frame must have been requested (otherwise ScheduleFrame() would not
- // have invoked this method), and coalesced with other requests that were
- // handled by a previous invocation of MaybeRenderFrame().
- return;
- }
-
- if (TooMuchBackPressure()) {
- // No need to request another frame; ScheduleFrame() will be called
- // when the back-pressure is relieved.
- FXL_VLOG(2)
- << "DefaultFrameScheduler::MaybeRenderFrame(): dropping frame, too "
- "much back-pressure.";
- return;
- }
-
// TODO(MZ-400): Check whether there is enough time to render. If not, drop
// a frame and reschedule. It would be nice to simply bump the presentation
// time, but then there is a danger of rendering too fast, and actually
@@ -231,13 +191,6 @@
// currently: the estimate is a hard-coded constant. Figure out what
// happened, and log it appropriately.
- // We are about to render a frame for the next scheduled presentation time, so
- // keep only the presentation requests for later times.
- while (!requested_presentation_times_.empty() &&
- presentation_time >= requested_presentation_times_.top()) {
- requested_presentation_times_.pop();
- }
-
// TODO(SCN-124): We should derive an appropriate value from the rendering
// targets, in particular giving priority to couple to the display refresh
// (vsync).
@@ -257,40 +210,51 @@
ApplyScheduledSessionUpdates(frame_timings->frame_number(),
presentation_time, presentation_interval);
- if (!any_updates_were_applied && !render_continuously_) {
+ if (!any_updates_were_applied && !render_pending_ &&
+ !render_continuously_) {
return;
}
+ if (currently_rendering_) {
+ render_pending_ = true;
+ return;
+ }
+
+ // Logging the first few frames to find common startup bugs.
if (frame_number_ < 5) {
FXL_LOG(INFO)
<< "DefaultFrameScheduler: calling RenderFrame presentation_time="
<< presentation_time << " frame_number=" << frame_number_;
}
+
// Render the frame
- if (delegate_.frame_renderer->RenderFrame(frame_timings, presentation_time,
- presentation_interval)) {
+ currently_rendering_ = delegate_.frame_renderer->RenderFrame(
+ frame_timings, presentation_time, presentation_interval);
+ if (currently_rendering_) {
outstanding_frames_.push_back(frame_timings);
+ render_pending_ = false;
}
}
// If necessary, schedule another frame.
- if (!requested_presentation_times_.empty()) {
- ScheduleFrame();
+ if (!updatable_sessions_.empty()) {
+ RequestFrame();
}
}
void DefaultFrameScheduler::ScheduleUpdateForSession(
- uint64_t presentation_time, scenic_impl::SessionId session_id) {
+ zx_time_t presentation_time, scenic_impl::SessionId session_id) {
updatable_sessions_.push({.session_id = session_id,
.requested_presentation_time = presentation_time});
- RequestFrame(presentation_time);
+ RequestFrame();
}
bool DefaultFrameScheduler::ApplyScheduledSessionUpdates(
- uint64_t frame_number, uint64_t presentation_time,
- uint64_t presentation_interval) {
+ uint64_t frame_number, zx_time_t presentation_time,
+ zx_duration_t presentation_interval) {
FXL_DCHECK(delegate_.session_updater);
+ // Logging the first few frames to find common startup bugs.
if (frame_number_ < 5) {
FXL_LOG(INFO) << "DefaultFrameScheduler::ApplyScheduledSessionUpdates "
"presentation_time="
@@ -355,29 +319,10 @@
}
outstanding_frames_.resize(outstanding_frames_.size() - 1);
- // If a frame was not scheduled due to back-pressure, try again.
- if (back_pressure_applied_) {
- // This will be reset if the next scheduled frame fails to render due to
- // back-pressure.
- back_pressure_applied_ = false;
-
- if (!requested_presentation_times_.empty()) {
- ScheduleFrame();
- }
- }
-
if (render_continuously_) {
- RequestFrame(0);
+ RequestFrame();
}
}
-bool DefaultFrameScheduler::TooMuchBackPressure() {
- if (outstanding_frames_.size() >= kMaxOutstandingFrames) {
- back_pressure_applied_ = true;
- return true;
- }
- return false;
-}
-
} // namespace gfx
} // namespace scenic_impl
diff --git a/garnet/lib/ui/gfx/engine/default_frame_scheduler.h b/garnet/lib/ui/gfx/engine/default_frame_scheduler.h
index cabdebc..f4cbab4 100644
--- a/garnet/lib/ui/gfx/engine/default_frame_scheduler.h
+++ b/garnet/lib/ui/gfx/engine/default_frame_scheduler.h
@@ -7,6 +7,7 @@
#include <queue>
+#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/zx/time.h>
#include "garnet/lib/ui/gfx/engine/frame_scheduler.h"
@@ -23,7 +24,7 @@
class DefaultFrameScheduler : public FrameScheduler {
public:
- explicit DefaultFrameScheduler(Display* const display);
+ explicit DefaultFrameScheduler(const Display* display);
~DefaultFrameScheduler();
// |FrameScheduler|
@@ -42,7 +43,7 @@
// Tell the FrameScheduler to schedule a frame. This is also used for
// updates triggered by something other than a Session update i.e. an
// ImagePipe with a new Image to present.
- void ScheduleUpdateForSession(uint64_t presentation_time,
+ void ScheduleUpdateForSession(zx_time_t presentation_time,
scenic_impl::SessionId session) override;
protected:
@@ -54,7 +55,7 @@
private:
// Used to compare presentation times so that the priority_queue acts as a min
- // heap, placing the earliest PresentationTime at the top
+ // heap, placing the earliest PresentationTime at the top.
class UpdatableSessionsComparator {
public:
bool operator()(SessionUpdate updatable_session1,
@@ -64,40 +65,22 @@
}
};
- // Request a frame to be scheduled at or after |presentation_time|, which
- // may be in the past.
- void RequestFrame(zx_time_t presentation_time);
+ // Requests a new frame to be drawn, which schedules the next wake up time for
+ // rendering. If we've already scheduled a wake up time, it checks if it needs
+ // rescheduling and deals with it appropriately.
+ void RequestFrame();
// Update the global scene and then draw it... maybe. There are multiple
// reasons why this might not happen. For example, the swapchain might apply
- // back-pressure if we can't hit our target frame rate. Or, after this frame
- // was scheduled, another frame was scheduled to be rendered at an earlier
- // time, and not enough time has elapsed to render this frame. Etc.
- void MaybeRenderFrame(zx_time_t presentation_time, zx_time_t wakeup_time,
- uint64_t flow_id);
+ // back-pressure if we can't hit our target frame rate. Or, the frame before
+ // this one has yet to finish rendering. Etc.
+ void MaybeRenderFrame(async_dispatcher_t*, async::TaskBase*, zx_status_t);
- // Schedule a frame for the earliest of |requested_presentation_times_|. The
- // scheduled time will be the earliest achievable time, such that rendering
- // can start early enough to hit the next Vsync.
- void ScheduleFrame();
-
- // Returns true to apply back-pressure when we cannot hit our target frame
- // rate. Otherwise, return false to indicate that it is OK to immediately
- // render a frame.
- // TODO(MZ-225): We need to track backpressure so that the frame scheduler
- // doesn't get too far ahead. With that in mind, Renderer::DrawFrame should
- // have a callback which is invoked when the frame is fully flushed through
- // the graphics pipeline. Then Engine::RenderFrame itself should have a
- // callback which is invoked when all renderers finish work for that frame.
- // Then FrameScheduler should listen to the callback to count how many
- bool TooMuchBackPressure();
-
- // Helper method for ScheduleFrame(). Returns the target presentation time
- // for the next frame, and a wake-up time that is early enough to start
- // rendering in order to hit the target presentation time.
- std::pair<zx_time_t, zx_time_t> ComputeNextPresentationAndWakeupTimes() const;
- // Computes the target presentation time for the requested presentation time.
- std::pair<zx_time_t, zx_time_t> ComputeTargetPresentationAndWakeupTimes(
+ // Computes the target presentation time for the requested presentation time,
+ // and a wake-up time that is early enough to start rendering in order to hit
+ // the target presentation time.
+ std::pair<zx_time_t, zx_time_t>
+ ComputePresentationAndWakeupTimesForTargetTime(
zx_time_t requested_presentation_time) const;
// Return the predicted amount of time required to render a frame.
@@ -106,30 +89,35 @@
// Executes updates that are scheduled up to and including a given
// presentation time. Returns true if rendering is needed.
bool ApplyScheduledSessionUpdates(uint64_t frame_number,
- uint64_t presentation_time,
- uint64_t presentation_interval);
+ zx_time_t presentation_time,
+ zx_duration_t presentation_interval);
+ // References.
async_dispatcher_t* const dispatcher_;
const Display* const display_;
+ FrameSchedulerDelegate delegate_;
- std::priority_queue<zx_time_t, std::vector<zx_time_t>,
- std::greater<zx_time_t>>
- requested_presentation_times_;
-
+ // State.
uint64_t frame_number_ = 0;
constexpr static size_t kMaxOutstandingFrames = 2;
std::vector<FrameTimingsPtr> outstanding_frames_;
- bool back_pressure_applied_ = false;
bool render_continuously_ = false;
+ bool currently_rendering_ = false;
+ bool render_pending_ = false;
+ zx_time_t wakeup_time_;
+ zx_time_t next_presentation_time_;
- // Lists all Session that have updates to apply, sorted by the earliest
- // requested presentation time of each update.
+ // The async task that wakes up to start rendering.
+ async::TaskMethod<DefaultFrameScheduler,
+ &DefaultFrameScheduler::MaybeRenderFrame>
+ frame_render_task_{this};
+
+ // Sessions that have updates to apply, sorted by requested presentation time
+ // from earliest to latest.
std::priority_queue<SessionUpdate, std::vector<SessionUpdate>,
UpdatableSessionsComparator>
updatable_sessions_;
- FrameSchedulerDelegate delegate_;
-
fxl::WeakPtrFactory<DefaultFrameScheduler> weak_factory_; // must be last
FXL_DISALLOW_COPY_AND_ASSIGN(DefaultFrameScheduler);
diff --git a/garnet/lib/ui/gfx/engine/engine.cc b/garnet/lib/ui/gfx/engine/engine.cc
index 1c58105..cd55a0e6 100644
--- a/garnet/lib/ui/gfx/engine/engine.cc
+++ b/garnet/lib/ui/gfx/engine/engine.cc
@@ -10,8 +10,8 @@
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
-#include <trace/event.h>
#include <lib/zx/time.h>
+#include <trace/event.h>
#include "garnet/lib/ui/gfx/engine/frame_scheduler.h"
#include "garnet/lib/ui/gfx/engine/frame_timings.h"
@@ -131,8 +131,8 @@
// Applies scheduled updates to a session. If the update fails, the session is
// killed. Returns true if a new render is needed, false otherwise.
bool Engine::UpdateSessions(std::vector<SessionUpdate> sessions_to_update,
- uint64_t frame_number, uint64_t presentation_time,
- uint64_t presentation_interval) {
+ uint64_t frame_number, zx_time_t presentation_time,
+ zx_duration_t presentation_interval) {
auto command_context = CreateCommandContext(frame_number);
bool needs_render = false;
@@ -172,8 +172,8 @@
}
bool Engine::RenderFrame(const FrameTimingsPtr& timings,
- uint64_t presentation_time,
- uint64_t presentation_interval) {
+ zx_time_t presentation_time,
+ zx_duration_t presentation_interval) {
// NOTE: this name is important for benchmarking. Do not remove or modify it
// without also updating the "process_gfx_trace.go" script.
TRACE_DURATION("gfx", "RenderFrame", "frame_number", timings->frame_number(),
diff --git a/garnet/lib/ui/gfx/engine/engine.h b/garnet/lib/ui/gfx/engine/engine.h
index f0c1b24..217b3f1 100644
--- a/garnet/lib/ui/gfx/engine/engine.h
+++ b/garnet/lib/ui/gfx/engine/engine.h
@@ -127,14 +127,14 @@
// Applies scheduled updates to a session. If the update fails, the session is
// killed. Returns true if a new render is needed, false otherwise.
bool UpdateSessions(std::vector<SessionUpdate> sessions_to_update,
- uint64_t frame_number, uint64_t presentation_time,
- uint64_t presentation_interval) override;
+ uint64_t frame_number, zx_time_t presentation_time,
+ zx_duration_t presentation_interval) override;
// |FrameRenderer|
//
// Renders a new frame. Returns true if successful, false otherwise.
- bool RenderFrame(const FrameTimingsPtr& frame, uint64_t presentation_time,
- uint64_t presentation_interval) override;
+ bool RenderFrame(const FrameTimingsPtr& frame, zx_time_t presentation_time,
+ zx_duration_t presentation_interval) override;
protected:
// Only used by subclasses used in testing.
diff --git a/garnet/lib/ui/gfx/engine/frame_scheduler.h b/garnet/lib/ui/gfx/engine/frame_scheduler.h
index 4d8047b..32f55de 100644
--- a/garnet/lib/ui/gfx/engine/frame_scheduler.h
+++ b/garnet/lib/ui/gfx/engine/frame_scheduler.h
@@ -17,7 +17,7 @@
class FrameTimings;
using FrameTimingsPtr = fxl::RefPtr<FrameTimings>;
-using PresentationTime = uint64_t;
+using PresentationTime = zx_time_t;
// Interface for performing session updates.
class SessionUpdater {
@@ -33,8 +33,9 @@
// session in |sessions|. Returns true if any updates were applied, false
// otherwise.
virtual bool UpdateSessions(std::vector<SessionUpdate> sessions_to_update,
- uint64_t frame_number, uint64_t presentation_time,
- uint64_t presentation_interval) = 0;
+ uint64_t frame_number,
+ zx_time_t presentation_time,
+ zx_duration_t presentation_interval) = 0;
};
// Interface for rendering frames.
@@ -53,8 +54,8 @@
// TODO(SCN-1089): these return value semantics are not ideal. See comments
// in Engine::RenderFrame() regarding this same issue.
virtual bool RenderFrame(const FrameTimingsPtr& frame_timings,
- uint64_t presentation_time,
- uint64_t presentation_interval) = 0;
+ zx_time_t presentation_time,
+ zx_duration_t presentation_interval) = 0;
};
struct FrameSchedulerDelegate {
@@ -84,7 +85,7 @@
// Tell the FrameScheduler to schedule a frame. This is also used for
// updates triggered by something other than a Session update i.e. an
// ImagePipe with a new Image to present.
- virtual void ScheduleUpdateForSession(uint64_t presentation_time,
+ virtual void ScheduleUpdateForSession(zx_time_t presentation_time,
scenic_impl::SessionId session) = 0;
protected:
diff --git a/garnet/lib/ui/gfx/tests/default_frame_scheduler_unittest.cc b/garnet/lib/ui/gfx/tests/default_frame_scheduler_unittest.cc
index 4964efa..a999678 100644
--- a/garnet/lib/ui/gfx/tests/default_frame_scheduler_unittest.cc
+++ b/garnet/lib/ui/gfx/tests/default_frame_scheduler_unittest.cc
@@ -37,7 +37,7 @@
// Schedule an update for in between the next two vsyncs.
zx_time_t time_after_vsync = fake_display_->GetLastVsyncTime() +
- (1.5 * fake_display_->GetVsyncInterval());
+ (1.5 * fake_display_->GetVsyncInterval());
scheduler->ScheduleUpdateForSession(/* presentation time*/ time_after_vsync,
/* session id */ 1);
@@ -59,6 +59,155 @@
EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
}
+TEST_F(FrameSchedulerTest, SinglePresent_ShouldGetSingleRenderCall) {
+ auto scheduler = CreateDefaultFrameScheduler();
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ scheduler->ScheduleUpdateForSession(Now().get(), /* session id */ 1);
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ // Wait for one vsync period.
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+ mock_renderer_->EndFrame();
+
+ // Present should have been scheduled and handled.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+
+ // Wait for a very long time.
+ RunLoopFor(zx::sec(10));
+ mock_renderer_->EndFrame();
+
+ // No further render calls should have been made.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+}
+
+TEST_F(FrameSchedulerTest, PresentsForTheSameFrame_ShouldGetSingleRenderCall) {
+ auto scheduler = CreateDefaultFrameScheduler();
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ SessionId session_id1 = 1;
+ SessionId session_id2 = 2;
+
+ // Schedule two updates for now.
+ zx_time_t now = Now().get();
+ scheduler->ScheduleUpdateForSession(now, session_id1);
+ scheduler->ScheduleUpdateForSession(now, session_id2);
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ // Wait for one vsync period.
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+ mock_renderer_->EndFrame();
+
+ // Both Presents should have been scheduled and handled.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+ ASSERT_EQ(mock_updater_->last_requested_updates().size(), 2u);
+ EXPECT_EQ(
+ mock_updater_->last_requested_updates()[0].requested_presentation_time,
+ now);
+
+ // Wait for a very long time.
+ RunLoopFor(zx::sec(10));
+ mock_renderer_->EndFrame();
+
+ // No further render calls should have been made.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+}
+
+TEST_F(FrameSchedulerTest,
+ PresentsForDifferentFrames_ShouldGetSeparateRenderCalls) {
+ auto scheduler = CreateDefaultFrameScheduler();
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ SessionId session_id = 1;
+
+ // Schedule an update for now.
+ zx_time_t now = Now().get();
+ scheduler->ScheduleUpdateForSession(now, session_id);
+
+ // Schedule an update for in between the next two vsyncs.
+ zx_time_t time_after_vsync = fake_display_->GetLastVsyncTime() +
+ (1.5 * fake_display_->GetVsyncInterval());
+ scheduler->ScheduleUpdateForSession(time_after_vsync, session_id);
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ // Wait for one vsync period.
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+ mock_renderer_->EndFrame();
+
+ // First Present should have been scheduled and handled.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+ ASSERT_EQ(mock_updater_->last_requested_updates().size(), 1u);
+ EXPECT_EQ(
+ mock_updater_->last_requested_updates()[0].requested_presentation_time,
+ now);
+
+ // Wait for one more vsync period.
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+ mock_renderer_->EndFrame();
+
+ // Second Present should have been scheduled and handled.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
+ ASSERT_EQ(mock_updater_->last_requested_updates().size(), 1u);
+ EXPECT_EQ(
+ mock_updater_->last_requested_updates()[0].requested_presentation_time,
+ time_after_vsync);
+}
+
+TEST_F(FrameSchedulerTest,
+ SecondPresentDuringRender_ShouldApplyUpdatesAndReschedule) {
+ auto scheduler = CreateDefaultFrameScheduler();
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 0u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 0u);
+
+ SessionId session_id = 1;
+
+ // Schedule an update for now.
+ zx_time_t now = Now().get();
+ scheduler->ScheduleUpdateForSession(now, session_id);
+
+ // Wait for one vsync period.
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 1u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+
+ // Schedule another update for now.
+ scheduler->ScheduleUpdateForSession(now, session_id);
+
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+
+ // Updates should be applied, but not rendered.
+ EXPECT_EQ(mock_updater_->update_sessions_call_count(), 2u);
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 1u);
+
+ // End previous frame.
+ mock_renderer_->EndFrame();
+
+ RunLoopFor(zx::duration(fake_display_->GetVsyncInterval()));
+
+ // Second render should have occured.
+ EXPECT_EQ(mock_renderer_->render_frame_call_count(), 2u);
+}
+
} // namespace test
} // namespace gfx
} // namespace scenic_impl
diff --git a/garnet/lib/ui/gfx/tests/frame_scheduler_mocks.h b/garnet/lib/ui/gfx/tests/frame_scheduler_mocks.h
index 1b42641..98fecf2 100644
--- a/garnet/lib/ui/gfx/tests/frame_scheduler_mocks.h
+++ b/garnet/lib/ui/gfx/tests/frame_scheduler_mocks.h
@@ -35,9 +35,10 @@
MockSessionUpdater() : weak_factory_(this) {}
bool UpdateSessions(std::vector<SessionUpdate> sessions_to_update,
- uint64_t frame_number, uint64_t presentation_time,
- uint64_t presentation_interval) override {
+ uint64_t frame_number, zx_time_t presentation_time,
+ zx_duration_t presentation_interval) override {
++update_sessions_call_count_;
+ last_requested_updates_ = std::move(sessions_to_update);
return update_sessions_return_value_;
};
@@ -46,6 +47,9 @@
update_sessions_return_value_ = new_value;
}
uint32_t update_sessions_call_count() { return update_sessions_call_count_; }
+ const std::vector<SessionUpdate>& last_requested_updates() {
+ return last_requested_updates_;
+ }
fxl::WeakPtr<MockSessionUpdater> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
@@ -54,6 +58,7 @@
private:
bool update_sessions_return_value_ = true;
uint32_t update_sessions_call_count_ = 0;
+ std::vector<SessionUpdate> last_requested_updates_;
fxl::WeakPtrFactory<MockSessionUpdater> weak_factory_; // must be last
};
@@ -63,8 +68,8 @@
MockFrameRenderer() : weak_factory_(this) {}
bool RenderFrame(const FrameTimingsPtr& frame_timings,
- uint64_t presentation_time,
- uint64_t presentation_interval) override {
+ zx_time_t presentation_time,
+ zx_duration_t presentation_interval) override {
++render_frame_call_count_;
last_frame_timings_ = frame_timings.get();
return render_frame_return_value_;
@@ -74,7 +79,7 @@
// FrameScheduler, but is not valid to do until after RenderFrame has returned
// to FrameScheduler. Hence this separate method.
void EndFrame() {
- if(last_frame_timings_) {
+ if (last_frame_timings_) {
last_frame_timings_->AddSwapchain(nullptr);
last_frame_timings_->OnFrameRendered(/*swapchain index*/ 0, /*time*/ 1);
last_frame_timings_->OnFramePresented(/*swapchain index*/ 0, /*time*/ 1);