帧调度

API 和计时模型

帧调度器开发

Pixel 的生命周期展示了客户端“展示”请求的情况 集成到风景框中

与客户签订合约

如果您想在 Present2 中使用 以确保帧调度正确且性能良好,请考虑 保证“风景”提供以下周围环境:

Vsync 准确度

Mountain 对未来 Vsync 的准确性或精确度不作任何保证 向客户提供的所有信息而仅仅是转发任何信息 可能不正确

发现了两个已发现的 Vsync 问题:轻微偏移 以及重大中断,且可能带来一些影响 Present2 客户端应该执行的操作 用途。

轻微 Vsync 偏移

其中一个问题是当 VSync 发生时(以微秒为单位) 也就是 ScapeS 的报告

假设在 FuturePresentationTimes 对象。然后,客户端调用 Present2 已请求 1, 000 微秒,预计其内容将以 1,000 微秒。不过,真正的 Vsync 实际上可能发生在 995us,客户端实际上会丢弃一帧,因为风景仅 尝试在下一 Vsync 中显示客户端内容。

因此建议客户端请求呈现的时间是 Vsync 的一半 与实际目标时间之间的时间间隔偏移,以便将此类偏移考虑在内。 与 Vsync 间隔无关的方式。

Vsync 严重中断

另一个问题是 Vsync 在应该达到的毫秒数后发生 以下 Vsync 恢复先前建立的模式。

假设显示屏的刷新率为 60Hz。然后,Vsync 将 发生在 0 毫秒、16.7 毫秒、33.3 毫秒、50 毫秒等的时间。在某些硬件上, 可能会有延迟这可能会导致 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() 如何发挥作用,都是不必要的。

Present2 最佳实践示例

这些示例展示了如何将该 API 用于不同的用例。通过 示例假定使用 API 的客户端已针对 OnFramePresentedOnScenicEvent 事件回调和 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();
  }
}