| // Copyright 2022 The Chromium 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 "components/exo/shell_surface_presentation_time_recorder.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/run_loop.h" |
| #include "base/time/time.h" |
| #include "components/exo/test/exo_test_base.h" |
| #include "components/exo/test/exo_test_helper.h" |
| #include "components/exo/test/shell_surface_builder.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "ui/gfx/presentation_feedback.h" |
| |
| using testing::ElementsAre; |
| |
| namespace exo { |
| namespace { |
| |
| class TestReporter : public ShellSurfacePresentationTimeRecorder::Reporter { |
| public: |
| TestReporter() = default; |
| ~TestReporter() override = default; |
| |
| // ShellSurfacePresentationTimeRecorder::Reporter: |
| void ReportTime(base::TimeDelta delta) override { ++report_count_; } |
| |
| int GetReportCountAndReset() { |
| int count = report_count_; |
| report_count_ = 0; |
| return count; |
| } |
| |
| private: |
| int report_count_ = 0; |
| }; |
| |
| class TestRecorder : public ShellSurfacePresentationTimeRecorder { |
| public: |
| TestRecorder(ShellSurface* shell_surface, std::unique_ptr<Reporter> reporter) |
| : ShellSurfacePresentationTimeRecorder(shell_surface, |
| std::move(reporter)) {} |
| |
| // ShellSurfacePresentationTimeRecorder: |
| void OnFramePresented(const Request& request, |
| const gfx::PresentationFeedback& feedback) override { |
| presented_serials_.push_back(request.serial.value()); |
| ShellSurfacePresentationTimeRecorder::OnFramePresented(request, |
| fake_feedback_); |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| |
| void WaitForFramePresented() { |
| base::RunLoop run_loop; |
| |
| base::AutoReset<base::RunLoop*> scoped(&run_loop_, &run_loop); |
| run_loop.Run(); |
| } |
| |
| std::vector<uint32_t> TakePresentedSerials() { |
| return std::move(presented_serials_); |
| } |
| |
| void set_fake_feedback(const gfx::PresentationFeedback& fake_feedback) { |
| fake_feedback_ = fake_feedback; |
| } |
| |
| private: |
| gfx::PresentationFeedback fake_feedback_; |
| base::RunLoop* run_loop_ = nullptr; |
| std::vector<uint32_t> presented_serials_; |
| }; |
| |
| } // namespace |
| |
| class ShellSurfacePresentationTimeRecorderTest : public test::ExoTestBase { |
| public: |
| ShellSurfacePresentationTimeRecorderTest() = default; |
| ~ShellSurfacePresentationTimeRecorderTest() override = default; |
| |
| // test::ExoTestBase: |
| void SetUp() override { |
| test::ExoTestBase::SetUp(); |
| |
| shell_surface_ = test::ShellSurfaceBuilder({32, 32}).BuildShellSurface(); |
| |
| auto reporter = std::make_unique<TestReporter>(); |
| reporter_ = reporter.get(); |
| recorder_ = std::make_unique<TestRecorder>(shell_surface_.get(), |
| std::move(reporter)); |
| } |
| void TearDown() override { |
| shell_surface_.reset(); |
| |
| test::ExoTestBase::TearDown(); |
| } |
| |
| void FakeFrameSubmitAndPresent() { |
| base::TimeDelta interval_not_used = base::Milliseconds(0); |
| // Create feedback with an extra 1s to ensure that presentation timestamp |
| // is later than the request time on slow bots. Otherwise, the presentation |
| // would not be reported and fail test expectations. |
| gfx::PresentationFeedback feedback( |
| base::TimeTicks().Now() + base::Milliseconds(1000), interval_not_used, |
| /*flags=*/0); |
| recorder_->set_fake_feedback(feedback); |
| |
| // Fake damage to ensure that Commit() generates a compositor frame. |
| root_surface()->Damage(gfx::Rect(0, 0, 32, 32)); |
| root_surface()->Commit(); |
| recorder_->WaitForFramePresented(); |
| } |
| |
| Surface* root_surface() { return shell_surface_->root_surface(); } |
| |
| protected: |
| std::unique_ptr<ShellSurface> shell_surface_; |
| std::unique_ptr<TestRecorder> recorder_; |
| TestReporter* reporter_ = nullptr; |
| }; |
| |
| TEST_F(ShellSurfacePresentationTimeRecorderTest, Request) { |
| // Request without "config" fails. |
| recorder_->PrepareToRecord(); |
| EXPECT_FALSE(recorder_->RequestNext()); |
| |
| // Fake a "Connfigure". |
| recorder_->OnConfigure(1u); |
| |
| // Request should succeed. |
| EXPECT_TRUE(recorder_->RequestNext()); |
| } |
| |
| TEST_F(ShellSurfacePresentationTimeRecorderTest, AckSkippedOrOutOfOrder) { |
| // Issue 4 requests with configure serial 1-5. |
| for (size_t i = 1u; i <= 5u; ++i) { |
| recorder_->PrepareToRecord(); |
| recorder_->OnConfigure(i); |
| ASSERT_TRUE(recorder_->RequestNext()); |
| } |
| |
| // Ack 2 and skip 1. |
| recorder_->OnAcknowledgeConfigure(2u); |
| |
| // FramePrsented would be reported for 1 and 2, even though 1 is not acked. |
| FakeFrameSubmitAndPresent(); |
| EXPECT_THAT(recorder_->TakePresentedSerials(), ElementsAre(1, 2)); |
| EXPECT_EQ(2, reporter_->GetReportCountAndReset()); |
| |
| // Ack 4 and 3 out of order. |
| recorder_->OnAcknowledgeConfigure(4u); |
| recorder_->OnAcknowledgeConfigure(3u); |
| recorder_->OnAcknowledgeConfigure(5u); |
| |
| // FramePresented would be reported for 3, 4, and 5, even though 3 and 4 |
| // is acked out of order. |
| FakeFrameSubmitAndPresent(); |
| EXPECT_THAT(recorder_->TakePresentedSerials(), ElementsAre(3, 4, 5)); |
| EXPECT_EQ(3, reporter_->GetReportCountAndReset()); |
| } |
| |
| TEST_F(ShellSurfacePresentationTimeRecorderTest, |
| RecorderDestroyedBeforePresent) { |
| // Create a pending request on the recorder. |
| recorder_->PrepareToRecord(); |
| recorder_->OnConfigure(1u); |
| EXPECT_TRUE(recorder_->RequestNext()); |
| recorder_->OnAcknowledgeConfigure(1u); |
| |
| // `recorder_` is gone before frame submission and presentation. `reporter_` |
| // is owned by `recorder_` so clear its reference too. |
| recorder_.reset(); |
| reporter_ = nullptr; |
| |
| // Fake frame submission. No FakeFrameSubmitAndPresent() because it depends |
| // on `recorder_`. |
| root_surface()->Commit(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(ShellSurfacePresentationTimeRecorderTest, |
| ShellSurfaceDestroyedBeforeRecorder) { |
| // Create a pending request on the recorder. |
| recorder_->PrepareToRecord(); |
| recorder_->OnConfigure(1u); |
| EXPECT_TRUE(recorder_->RequestNext()); |
| recorder_->OnAcknowledgeConfigure(1u); |
| |
| // ShellSurface gets destroyed before recorder. |
| shell_surface_.reset(); |
| } |
| |
| } // namespace exo |