影格排程

API 和時間模型

Frame Scheduler 開發

Pixel 的生命週期顯示客戶展示要求如何整合至風景頁框中。

查看與客戶之間的良好合約

如要使用 Present2,且影格排程正確且效能良好,請考慮使用 View 提供的環境保證:

Vsync 準確率

View 不保證未來提供給用戶端的 Vsync 資訊的正確性或精確度。Display & Video 360 只會轉送從硬體收到的任何資訊,但可能不正確的資訊。

我們發現兩個 Vsync 問題 (輕微偏移重大服務中斷) 可能會影響 Present2 用戶端應採取的行動。

次要 Vsync 偏移

其中一個問題是,在觀看報表的時間之前 (以微秒為單位) 發生 VSync。

假設用戶端在 FuturePresentationTimes 物件中收到 1000us 的未來 Vsync 時間。接著,用戶端會呼叫 Present2,要求的顯示時間為 1000us,預期其內容將顯示為 1000us。不過,真正的 Vsync 可能確實發生在 995us 處,因此用戶端會有效捨棄影格,因為 View 只會嘗試在下列 Vsync 中顯示用戶端的內容。

因此,我們建議用戶端要求呈現顯示時間比實際目標時間差一半的 Vsync 間隔偏移,以便獨立於 Vsync 間隔作業中獨立計算這項偏移情況。

重大 Vsync 服務中斷問題

另一個問題是 Vsync 作業出現後的毫秒數,下列 Vsync 會恢復先前建立的模式。

假設螢幕的刷新率為 60 Hz。您可以預期 Vsync 會在 0 毫秒、16.7 毫秒、33.3 毫秒、50 毫秒等時間發生。在某些硬體上,Vsync 可能會延遲。這可能會導致 Vsync 發生時間為 0 毫秒、22 毫秒、33.3 毫秒、50 毫秒等。

這個問題偶爾會發生,而且不會發出警告,因此沒有任何方法能有效防止這個問題發生。為了減輕影響,用戶端應格外注意,確保其 requested_presentation_time 保持單調遞增。否則用戶端工作階段就會關閉。

如要讓 requested_presentation_time 維持單調遞增,請在排程程式碼中加入以下邏輯:

// Do some operations.
requested_presentation_time = CalculateNextPresentationTime();
// Lower bound the requested time.
requested_presentation_time = std::max(requested_presentation_time, previous_requested_presentation_time);

previous_requested_presentation_time = requested_presentation_time;
session->Present2(requested_presentation_time, ...);

這個程式碼是正確的,因為新的要求時間一律會大於或等於舊要求時間。

這個程式碼很實用,因為無論 CalculateNextPresentationTime() 的用途為何,都不會導致您不必要地遺漏影格。

簡報 2 最佳做法範例

這些示例說明 API 如何用於不同用途。範例中假設使用 API 的用戶端已為 OnFramePresentedOnScenicEvent 事件回呼註冊事件監聽器,且 session 是初始化的 View Session 管道。如需如何設定風景的範例,請參閱煙火範例

示例 1

最簡單的應用程式類型,每次出現先前的更新時,就會顯示新的更新。對於工作負載較小 (建立影格所需的時間較短) 且沒有將延遲時間降到最低的要求,這是合理的做法。


void main() {
  PresentNewFrame();
}

void PresentNewFrame() {
  // Create a new update and enquee it.
  CreateAndEnqueueNewUpdate();

  // Flush enqueued events.
  Present2Args args;
  args.set_requested_presentation_time(0);
  args.set_acquire_fences({});
  args.set_release_fences({});
  args.set_requested_prediction_span(0);
  session->Present2(std::move(args), /*callback=*/[]{});
}

void OnFramePresented(...) {
  PresentNewFrame();
}

void CreateAndEnqueueNewUpdate() {
   // Enqueue commands to update the session
}


示例 2

本範例說明如何使用小型的輸入驅動更新來編寫應用程式,而盡可能縮短延遲時間是很重要的事。它會在接收輸入內容時建立新的小型更新,並立即呼叫 Present2 嘗試盡可能縮短延遲時間。此方法僅應用於極小型的工作負載,因為不批次更新建立會產生一些不必要的工作。


int64 num_calls_left_ = 0;
bool update_pending_ = false;

main() {
  session->RequestPresentationTimes(/*requested_prediction_span=*/0,
                     /*callback=*/[this](FuturePresentationTimes future_times){
    UpdateNumCallsLeft(future_times.remaining_presents_in_flight_allowed);
  };}
}

void UpdateNumCallsLeft(int64_t num_calls_left){
  num_calls_left_ = num_calls_left;
}

void OnScenicEvent(Event event) {
  if (IsInputEvent(event)) {
    CreateAndEnqueueUpdate(std::move(event));
    if (num_calls_left_ > 0) {
      PresentFrame();
    } else {
      update_pending_ = true;
    }
  }
}

void PresentFrame() {
  --num_calls_left_;
  update_pending_ = false;

  Present2Args args;
  args.set_requested_presentation_time(0);
  args.set_acquire_fences({});
  args.set_release_fences({});
  args.set_requested_prediction_span(0);
  session->Present2(std::move(args),
                    /*callback=*/[this](FuturePresentationTimes future_times){
    UpdateNumCallsLeft(future_times.remaining_presents_in_flight_allowed);
  };);
}

void OnFramePresented(FramePresentedInfo info) {
    UpdateNumCallsLeft(info.num_presents_allowed);
  if (frame_pending_ && num_calls_left_ > 0) {
     PresentFrame();
  }
}


示例 3

這個範例說明如何編寫以輸入驅動的應用程式批次處理輸入。


struct TargetTimes {
  zx_time_t latch_point;
  zx_time_t presentation_time;
};

int64 num_calls_left_ = 0;
bool update_pending_ = false;
bool frame_pending_ = false;
zx_time_t last_targeted_time_ = 0;
std::vector<Event> unhandled_input_events_;
async_dispatcher dispatcher_;

void UpdateNumCallsLeft(int64_t num_calls_left){
  num_calls_left_ = num_calls_left;
}

zx_duration_t UpdateCreationTime() {
  // Return a prediction for how long an update could take to create.
}

TargetTimes FindNextPresentationTime(
                      std::vector<PresentationInfo> future_presentations) {
  // Select the next future time to target.
  zx_time_t now = time.Now();
  for(auto times : future_presentations) {
    if (times.latch_point > now + UpdateCreationTime()
        && times.presentation_time > last_targeted_time_) {
      return {times.latch_point, times.presentation_time};
    }
  }

  // This should never be reached.
  return {now, now};
}

void CreateAndEnqueueNewUpdate(std::vector<Event> input_events) {
   // Enqueue commands to update the session.
}

void OnScenicEvent(Event event) {
  if (IsInputEvent(event)) {
    unhandled_input_events_.push_back(std::move(event));
    RequestNewFrame();
  }
}

void RequestNewFrame() {
  if (update_pending_) {
    return;
  } else {
    update_pending_ = true;
    ScheduleNextFrame();
  }
}

void PresentFrame() {
  present_pending_ = false;

  session->RequestPresentationTimes(/*requested_prediction_span=*/0,
                    /*callback=*/[this](FuturePresentationTimes future_times){
    if (future_times.remaining_presents_in_flight_allowed > 0) {
      // No present calls left. Need to wait to be returned some by previous
      // frames being completed in OnFramePresented(). This could happen when
      // Scenic gets overwhelmed or stalled for some reason.
      present_pending_ = true;
      return;
    }

    TargetTimes target_times =
          FindNextPresentationTime(future_times.future_presentations);
    last_targeted_time_ = target_time.presentation_time;


    // Wait until slightly before the deadline to start creating the update.
    zx_time_t wakeup_time = target_times.latch_point - UpdateCreationTime();
    async::PostTaskForTime(
      dispatcher_,
      [this, presentation_time] {
        update_pending_ = false;
        present_pending_ = false;

        CreateAndEnqueueUpdate(std::move(unhandled_input_events_));

        Present2Args args;
        // We subtract a bit from our requested time (1 ms in this example)
        // for two reasons:
        // 1. Future presentation times aren't guaranteed to be entirely
        // accurate due to hardware vsync drift and other factors.
        // 2. A presetnt call is guaranteed to be presented "at or later than"
        // the requested presentation time.
        // This guards against against `Present2` calls getting accidentally
        // delayed for an entire frame.
        args.set_requested_presentation_time(
              target_times.presentation_time - 1'000'000);
        args.set_acquire_fences({});
        args.set_release_fences({});
        args.set_requested_prediction_span(0);
        session->Present2(std::move(args), /*callback=*/[]{};);
      },
      wakeup_time);
  };}
}

void OnFramePresented(FramePresentedInfo info) {
  if (frame_pending_ && info.num_presents_allowed > 0) {
     PresentFrame();
  }
}