帧调度

API 和计时模型

帧调度程序开发

Pixel 的生命周期展示了客户端展示请求如何集成到“景观”帧中。

与客户签订这份合同

如果您想以正确且高效的帧调度方式使用 Present2,请考虑 Insights 可为以下环境提供的保证:

Vsync 准确性

对于将来提供给客户端的 Vsync 信息的准确性或精确度,Sensess 不做任何保证。Frameworks 只会转发它从硬件接收的任何可能不正确的信息。

已识别两个 Vsync 问题,即轻微偏移严重中断,并对 Present2 客户端应执行的操作造成一些影响。

轻微 Vsync 偏移

一个问题是,VSync 发生的时间(以微秒为单位)早于 Insights 报告时间。

假设客户端在 FuturePresentationTimes 对象中收到的 Vsync 时间为 1, 000 微秒。然后,客户端会调用 Present2,并将请求的呈现时间设置为 1,000 微秒,并预期其内容会显示为 1,000 微秒。不过,真正的 Vsync 可能实际上发生在 995us 处,并且客户端实际上会丢弃一帧,因为“Scece”只会尝试在以下 Vsync 中显示客户端的内容。

因此,建议客户端在请求呈现时选择其实际目标时间的一半 Vsync 间隔时间,以以不依赖于 Vsync 间隔的方式解释这种偏移。

严重的 Vsync 中断

另一个问题是 Vsync 发生在预期延迟后的毫秒数内,随后 Vsync 会恢复先前建立的模式。

假设显示屏的刷新率为 60Hz。然后,预计 Vsync 会在 0 毫秒、16.7 毫秒、33.3 毫秒、50 毫秒等时间发生。在某些硬件上,Vsync 可能会延迟。这可能会导致在 0 毫秒、22 毫秒、33.3 毫秒、50 毫秒等时间发生 Vsync。

此问题偶尔会发生,并且没有任何警告,因此无法有效防范。为了减轻其影响,客户端应格外小心,以确保其 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() 执行什么操作,它都不会不必要地导致您错过帧。

Present2 最佳做法示例

这些示例展示了如何将 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();
  }
}