blob: c88338575532eca83a8e6a326be7d132927bbedc [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ftl/optional.h>
#include <gtest/gtest.h>
#include <common/test/FlagUtils.h>
#include <scheduler/Fps.h>
#include <scheduler/FrameTargeter.h>
#include <scheduler/IVsyncSource.h>
#include <com_android_graphics_surfaceflinger_flags.h>
using namespace std::chrono_literals;
namespace android::scheduler {
namespace {
struct VsyncSource final : IVsyncSource {
VsyncSource(Period period, Period minFramePeriod, TimePoint deadline)
: vsyncPeriod(period), framePeriod(minFramePeriod), vsyncDeadline(deadline) {}
const Period vsyncPeriod;
const Period framePeriod;
const TimePoint vsyncDeadline;
Period period() const override { return vsyncPeriod; }
TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
Period minFramePeriod() const override { return framePeriod; }
};
} // namespace
class FrameTargeterTest : public testing::Test {
public:
const auto& target() const { return mTargeter.target(); }
struct Frame {
Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
Duration frameDuration, Fps refreshRate, Fps peakRefreshRate,
FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled,
const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt)
: testPtr(testPtr),
frameBeginTime(frameBeginTime),
period(refreshRate.getPeriod()),
minFramePeriod(peakRefreshRate.getPeriod()) {
const FrameTargeter::BeginFrameArgs args{.frameBeginTime = frameBeginTime,
.vsyncId = vsyncId,
.expectedVsyncTime =
frameBeginTime + frameDuration,
.sfWorkDuration = 10ms};
testPtr->mTargeter.beginFrame(args,
vsyncSourceOpt
.or_else([&] {
return std::make_optional(
VsyncSource(period, period,
args.expectedVsyncTime));
})
.value(),
isFencePendingFuncPtr);
}
FenceTimePtr end(CompositionCoverage coverage = CompositionCoverage::Hwc) {
if (ended) return nullptr;
ended = true;
auto [fence, fenceTime] = testPtr->mFenceMap.makePendingFenceForTest();
testPtr->mTargeter.setPresentFence(std::move(fence), fenceTime);
testPtr->mTargeter.endFrame({.compositionCoverage = coverage});
return fenceTime;
}
~Frame() {
end();
frameBeginTime += period;
}
static bool fencePending(const FenceTimePtr&, int) { return true; }
static bool fenceSignaled(const FenceTimePtr&, int) { return false; }
FrameTargeterTest* const testPtr;
TimePoint& frameBeginTime;
const Period period;
const Period minFramePeriod;
bool ended = false;
};
private:
FenceToFenceTimeMap mFenceMap;
static constexpr bool kBackpressureGpuComposition = true;
FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition};
};
TEST_F(FrameTargeterTest, targetsFrames) {
VsyncId vsyncId{42};
{
TimePoint frameBeginTime(989ms);
const Frame frame(this, vsyncId++, frameBeginTime, 10ms, 60_Hz, 60_Hz);
EXPECT_EQ(target().vsyncId(), VsyncId{42});
EXPECT_EQ(target().frameBeginTime(), TimePoint(989ms));
EXPECT_EQ(target().expectedPresentTime(), TimePoint(999ms));
EXPECT_EQ(target().expectedFrameDuration(), 10ms);
}
{
TimePoint frameBeginTime(1100ms);
const Frame frame(this, vsyncId++, frameBeginTime, 11ms, 60_Hz, 60_Hz);
EXPECT_EQ(target().vsyncId(), VsyncId{43});
EXPECT_EQ(target().frameBeginTime(), TimePoint(1100ms));
EXPECT_EQ(target().expectedPresentTime(), TimePoint(1111ms));
EXPECT_EQ(target().expectedFrameDuration(), 11ms);
}
}
TEST_F(FrameTargeterTest, inflatesExpectedPresentTime) {
// Negative such that `expectedVsyncTime` is in the past.
constexpr Duration kFrameDuration = -3ms;
TimePoint frameBeginTime(777ms);
constexpr Fps kRefreshRate = 120_Hz;
const VsyncSource vsyncSource(kRefreshRate.getPeriod(), kRefreshRate.getPeriod(),
frameBeginTime + 5ms);
const Frame frame(this, VsyncId{123}, frameBeginTime, kFrameDuration, kRefreshRate,
kRefreshRate, Frame::fenceSignaled, vsyncSource);
EXPECT_EQ(target().expectedPresentTime(), vsyncSource.vsyncDeadline + vsyncSource.vsyncPeriod);
}
TEST_F(FrameTargeterTest, recallsPastVsync) {
VsyncId vsyncId{111};
TimePoint frameBeginTime(1000ms);
constexpr Fps kRefreshRate = 60_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
constexpr Duration kFrameDuration = 13ms;
for (int n = 5; n-- > 0;) {
Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
const auto fence = frame.end();
EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), fence);
}
}
TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
VsyncId vsyncId{222};
TimePoint frameBeginTime(2000ms);
constexpr Fps kRefreshRate = 120_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
constexpr Duration kFrameDuration = 10ms;
FenceTimePtr previousFence = FenceTime::NO_FENCE;
for (int n = 5; n-- > 0;) {
Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
const auto fence = frame.end();
EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
previousFence = fence;
}
}
TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
VsyncId vsyncId{222};
TimePoint frameBeginTime(2000ms);
constexpr Fps kRefreshRate = 120_Hz;
constexpr Fps kPeakRefreshRate = 240_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
constexpr Duration kFrameDuration = 10ms;
FenceTimePtr previousFence = FenceTime::NO_FENCE;
for (int n = 5; n-- > 0;) {
Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
kPeakRefreshRate);
const auto fence = frame.end();
EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
previousFence = fence;
}
}
TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
constexpr Period kPeriod = (60_Hz).getPeriod();
EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
}
TEST_F(FrameTargeterTest, detectsEarlyPresent) {
VsyncId vsyncId{333};
TimePoint frameBeginTime(3000ms);
constexpr Fps kRefreshRate = 60_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
// The target is not early while past present fences are pending.
for (int n = 3; n-- > 0;) {
const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
}
// The target is early if the past present fence was signaled.
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
const auto fence = frame.end();
fence->signalForTest(frameBeginTime.ns());
EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
}
TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
VsyncId vsyncId{444};
TimePoint frameBeginTime(4000ms);
constexpr Fps kRefreshRate = 120_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
// The target is not early while past present fences are pending.
for (int n = 3; n-- > 0;) {
const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
}
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
const auto fence = frame.end();
fence->signalForTest(frameBeginTime.ns());
// The target is two VSYNCs ahead, so the past present fence is still pending.
EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
{ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
// The target is early if the past present fence was signaled.
EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
}
TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
TimePoint frameBeginTime(5000ms);
constexpr Fps kRefreshRate = 144_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
// The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
}
TEST_F(FrameTargeterTest, detectsMissedFrames) {
VsyncId vsyncId{555};
TimePoint frameBeginTime(5000ms);
constexpr Fps kRefreshRate = 60_Hz;
constexpr Period kPeriod = kRefreshRate.getPeriod();
EXPECT_FALSE(target().isFramePending());
EXPECT_FALSE(target().didMissFrame());
EXPECT_FALSE(target().didMissHwcFrame());
{
const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().isFramePending());
// The frame did not miss if the past present fence is invalid.
EXPECT_FALSE(target().didMissFrame());
EXPECT_FALSE(target().didMissHwcFrame());
}
{
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
Frame::fencePending);
EXPECT_TRUE(target().isFramePending());
// The frame missed if the past present fence is pending.
EXPECT_TRUE(target().didMissFrame());
EXPECT_TRUE(target().didMissHwcFrame());
frame.end(CompositionCoverage::Gpu);
}
{
const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
Frame::fencePending);
EXPECT_TRUE(target().isFramePending());
// The GPU frame missed if the past present fence is pending.
EXPECT_TRUE(target().didMissFrame());
EXPECT_FALSE(target().didMissHwcFrame());
}
{
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().isFramePending());
const auto fence = frame.end();
const auto expectedPresentTime = target().expectedPresentTime();
fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2 + 1);
}
{
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().isFramePending());
const auto fence = frame.end();
const auto expectedPresentTime = target().expectedPresentTime();
fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2);
// The frame missed if the past present fence was signaled but not within slop.
EXPECT_TRUE(target().didMissFrame());
EXPECT_TRUE(target().didMissHwcFrame());
}
{
Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
EXPECT_FALSE(target().isFramePending());
// The frame did not miss if the past present fence was signaled within slop.
EXPECT_FALSE(target().didMissFrame());
EXPECT_FALSE(target().didMissHwcFrame());
}
}
} // namespace android::scheduler