Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions nativelib/src/main/cpp/presentation_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@ void PtsPresentationScheduler::Configure(double fps) {
std::llround(static_cast<double>(kNanosecondsPerSecond) / safeFps));
frameIntervalNs_ = std::max<int64_t>(frameIntervalNs_, 1000000LL);
// RenderService needs an absolute latch margin that does not shrink with
// the stream frame interval. Keep steady-state lead small, but large enough
// for 120 Hz submission overhead.
// the stream frame interval. Host-paced presentation also keeps one full
// stream frame decoded ahead so normal decoder jitter does not immediately
// miss a VSync slot.
submitLeadNs_ = kSubmitLeadNs;
initialLeadNs_ = submitLeadNs_;
initialLeadNs_ = submitLeadNs_ + frameIntervalNs_;
maxFutureLeadNs_ = initialLeadNs_ +
CalculateFutureCadenceBudgetNs(safeFps, frameIntervalNs_) +
kPtsQuantizationSlackNs;
Expand Down Expand Up @@ -127,8 +128,8 @@ PresentationPlan PtsPresentationScheduler::ScheduleTarget(
}
}

// Start the existing cadence budget at the first slot that still has the
// submit margin. Being close to a VSync must not reduce burst capacity.
// Start the queue budget at the first slot that still has the submit
// margin. Being close to a VSync must not reduce burst capacity.
const int64_t firstEligibleSlotNs = CeilToSlot(
requiredTargetNs, slotClock.anchorNs, slotClock.periodNs);
const int64_t maxScheduledSlotNs = firstEligibleSlotNs +
Expand Down Expand Up @@ -179,9 +180,12 @@ bool PtsPresentationScheduler::IsPresentationQueueEmpty(

int64_t PtsPresentationScheduler::GetAdditionalQueueSlots(
int64_t vsyncPeriodNs) const {
const int64_t cadenceBudgetNs = std::max<int64_t>(
0, maxFutureLeadNs_ - initialLeadNs_);
return cadenceBudgetNs / vsyncPeriodNs;
// Count from the first slot that satisfies the RenderService submit margin.
// This includes the one-frame playout reserve plus the existing burst
// budget, so adding the reserve does not reduce burst capacity.
const int64_t queueLeadNs = std::max<int64_t>(
0, maxFutureLeadNs_ - submitLeadNs_);
return queueLeadNs / vsyncPeriodNs;
}

void PtsPresentationScheduler::ApplySlowDriftCorrection(
Expand Down
4 changes: 2 additions & 2 deletions nativelib/src/main/cpp/presentation_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ class PtsPresentationScheduler {
int64_t GetAdditionalQueueSlots(int64_t vsyncPeriodNs) const;

int64_t frameIntervalNs_ = 16666667LL;
int64_t initialLeadNs_ = 2000000LL;
int64_t initialLeadNs_ = 18666667LL;
int64_t submitLeadNs_ = 2000000LL;
int64_t maxFutureLeadNs_ = 10334333LL;
int64_t maxFutureLeadNs_ = 27001000LL;
int64_t discontinuityNs_ = 250000000LL;

bool initialized_ = false;
Expand Down
36 changes: 29 additions & 7 deletions nativelib/src/test/cpp/presentation_scheduler_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ void AssertFutureCadenceBudget(double fps, int numerator, int denominator) {
const int64_t expectedCadenceBudgetNs =
FrameIntervalNs(fps) * numerator / denominator;

assert(scheduler.GetInitialLeadNs() == kExpectedSubmitLeadNs);
const int64_t expectedInitialLeadNs =
kExpectedSubmitLeadNs + FrameIntervalNs(fps);
assert(scheduler.GetInitialLeadNs() == expectedInitialLeadNs);
assert(scheduler.GetMaxFutureLeadNs() ==
kExpectedSubmitLeadNs + expectedCadenceBudgetNs +
expectedInitialLeadNs + expectedCadenceBudgetNs +
kNanosecondsPerMicrosecond);
}

Expand All @@ -52,6 +54,25 @@ void TestRefreshRateTierBudgets() {
AssertFutureCadenceBudget(144.0, 2, 1);
}

void TestOneFrameReserveAbsorbsSubFrameDecodeJitter() {
PtsPresentationScheduler scheduler;
scheduler.Configure(120.0);
const PresentationVsyncTiming timing = Timing(120.0);
const int64_t firstDecodedAtNs = kStartNs + kMs;

const PresentationPlan first = scheduler.PlanFrame(
0, firstDecodedAtNs, timing);
const PresentationPlan jittered = scheduler.PlanFrame(
8334, firstDecodedAtNs + timing.periodNs + 5 * kMs, timing);
const int64_t firstEligibleSlotNs = kStartNs + timing.periodNs;

AssertOnVsyncSlot(first, timing);
AssertOnVsyncSlot(jittered, timing);
assert(first.targetTimeNs == firstEligibleSlotNs + timing.periodNs);
assert(jittered.event == PresentationEvent::NONE);
assert(jittered.targetTimeNs == first.targetTimeNs + timing.periodNs);
}

void Test120FpsBurstUsesThreeUniqueSlots() {
PtsPresentationScheduler scheduler;
scheduler.Configure(120.0);
Expand Down Expand Up @@ -92,7 +113,7 @@ void TestQueueFullWaitsForDrainBeforeReanchor() {
assert(waiting.event == PresentationEvent::WAIT_FOR_DRAIN);
assert(recovered.action == PresentationAction::SCHEDULE);
assert(recovered.event == PresentationEvent::CATCH_UP);
assert(recovered.targetTimeNs == third.targetTimeNs + timing.periodNs);
assert(recovered.targetTimeNs == third.targetTimeNs + 2 * timing.periodNs);
}

void Test90FpsPairBurstUsesTwoSlots() {
Expand Down Expand Up @@ -128,7 +149,7 @@ void Test60FpsDoesNotGrowPresentationQueue() {
assert(burst.event == PresentationEvent::QUEUE_FULL);
assert(recovered.action == PresentationAction::SCHEDULE);
assert(recovered.event == PresentationEvent::CATCH_UP);
assert(recovered.targetTimeNs == first.targetTimeNs + timing.periodNs);
assert(recovered.targetTimeNs == first.targetTimeNs + 2 * timing.periodNs);
}

void TestSmallLateFrameShiftsWholeTimeline() {
Expand All @@ -139,9 +160,9 @@ void TestSmallLateFrameShiftsWholeTimeline() {
const PresentationPlan first = scheduler.PlanFrame(
0, kStartNs + kMs, timing);
const PresentationPlan shifted = scheduler.PlanFrame(
16667, kStartNs + 18 * kMs, timing);
16667, kStartNs + 35 * kMs, timing);
const PresentationPlan next = scheduler.PlanFrame(
33333, kStartNs + 34 * kMs, timing);
33333, kStartNs + 51 * kMs, timing);

assert(shifted.action == PresentationAction::SCHEDULE);
assert(shifted.event == PresentationEvent::PHASE_SHIFT);
Expand Down Expand Up @@ -183,7 +204,7 @@ void TestDiscontinuityWaitsForQueuedSlot() {
assert(waiting.event == PresentationEvent::WAIT_FOR_DRAIN);
assert(reanchored.action == PresentationAction::SCHEDULE);
assert(reanchored.event == PresentationEvent::DISCONTINUITY);
assert(reanchored.targetTimeNs == first.targetTimeNs + timing.periodNs);
assert(reanchored.targetTimeNs == first.targetTimeNs + 2 * timing.periodNs);
}

void TestDuplicatePtsRecoversAfterQueuedSlot() {
Expand Down Expand Up @@ -263,6 +284,7 @@ void TestInvalidPtsDropsWithoutScheduling() {

int main() {
TestRefreshRateTierBudgets();
TestOneFrameReserveAbsorbsSubFrameDecodeJitter();
Test120FpsBurstUsesThreeUniqueSlots();
TestQueueFullWaitsForDrainBeforeReanchor();
Test90FpsPairBurstUsesTwoSlots();
Expand Down
Loading