API 與時間模型
影格排程器開發
Pixel 的生命週期,呈現客戶的螢幕畫面分享請求 整合到畫面範圍內
與客戶簽定的書面合約
如果要在Present2
才能正確且成效良好
保證風景可傳遞周圍的環境:
Vsync 準確度
「刪除」不保證未來 Vsync 的準確性或精確度 也不必擔心畫面完全仰賴資訊 但卻可能不正確。
我們發現 Vsync 有兩個問題:輕微偏移
和重大干擾,可能會影響
Present2
客戶應該採取哪些做法
信任關係
次要 Vsync 偏移
其中一個問題是 VSync 在時間之前 (以微秒為單位) 「查看」報表
假設用戶端在
FuturePresentationTimes
敬上
物件。接著,用戶端會呼叫
使用請求的 Present2
1000 的播放時間,並預期其內容會在
1000 我們。然而,真正的 Vsync 事件實際上可能會在
995 求值,而客戶會有效地捨棄相框,因為僅有 View 只會
嘗試在以下 Vsync 時顯示用戶端的內容。
因此,建議用戶端要求展示次數為一半的 Vsync 時間 與實際目標時間的差距,以便計入 同步間隔,各自獨立。
Vsync 的重大中斷
另一個問題是 Vsync 會在預期發生幾毫秒後發生。 並使用下列 Vsync,恢復先前建立的模式。
假設螢幕刷新率為 60Hz。這樣 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 的用戶端已針對
OnFramePresented
和
OnScenicEvent
事件回呼,以及
session
是初始化的 View
工作階段
頻道。如需如何設定景觀的範例,請參閱
情境示例。
示例 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();
}
}