Frame Pacing Library Thuộc Android Game Development Kit.
Thư viện Android Frame Pacing, còn gọi là Swappy, là một phần của Thư viện AGDK. Điều này giúp các trò chơi OpenGL và Vulkan đạt được khả năng kết xuất hình ảnh mượt mà và tốc độ khung hình chính xác trên Android. Tài liệu này xác định tốc độ khung hình, mô tả các tình huống cần sử dụng tốc độ khung hình và cho thấy cách thư viện giải quyết các tình huống này. Nếu bạn muốn chuyển thẳng đến cách triển khai tốc độ khung hình trong trò chơi của mình, hãy xem Bước tiếp theo.
Thông tin khái quát
Tốc độ khung hình là sự đồng bộ hoá vòng lặp logic trò chơi và kết xuất đồ hoạ với hệ thống hiển thị phụ của hệ điều hành và phần cứng hiển thị cơ bản. Hệ thống hiển thị phụ của Android được thiết kế để tránh bất thường về mặt hình ảnh (còn gọi là xé hình) có thể xảy ra khi phần cứng hiển thị chuyển sang một khung mới một phần thông qua bản cập nhật. Để tránh hiển thị những bất thường này, hệ thống hiển thị phụ sẽ làm như sau:
- Đệm các khung hình trước đây trong nội bộ
- Phát hiện khung hình gửi chậm
- Lặp lại quá trình hiển thị các khung hình trước đây khi phát hiện thấy khung hình trễ
Một trò chơi thông báo cho
SurfaceFlinger,
trình kết hợp trong hệ thống hiển thị phụ, rằng đã gửi tất cả các lệnh gọi
cần thiết cho một khung hình (bằng cách gọi eglSwapBuffers
hoặc vkQueuePresentKHR
).
SurfaceFlinger báo hiệu rằng một khung hình đã sẵn sàng để được phần cứng hiển thị bằng cách sử dụng
một chốt. Sau đó, phần cứng hiển thị sẽ trình chiếu khung hình đó. Phần cứng hiển thị
hoạt động ở tốc độ không đổi (ví dụ: 60 Hz) và nếu không có khung hình mới
để hiển thị thì phần cứng sẽ hiển thị lại khung trước đó.
Thời gian kết xuất khung hình không nhất quán thường xảy ra khi vòng lặp kết xuất của trò chơi hiển thị ở một tốc độ khác với tốc độ của phần cứng hiển thị gốc. Nếu một trò chơi đang chạy ở tốc độ 30 FPS (khung hình/giây) cố gắng hiển thị trên một thiết bị vốn hỗ trợ tốc độ 60 FPS, thì vòng lặp kết xuất trò chơi sẽ không nhận ra rằng một khung hình lặp lại sẽ tồn tại trên màn hình thêm 16 mili giây. Việc thiếu liên kết này thường tạo ra một sự không nhất quán đáng kể về thời gian kết xuất khung hình (chẳng hạn như: 49 mili giây, 16 mili giây, 33 mili giây). Các cảnh quá phức tạp có thể gây ra vấn đề phức tạp hơn nữa, vì có thể làm xảy ra việc bỏ lỡ các khung hình.
Các giải pháp không tối ưu
Các giải pháp sau đây về tốc độ khung hình đã được các trò chơi sử dụng trước đây và thường dẫn đến thời gian kết xuất khung hình không nhất quán và độ trễ đầu vào tăng lên.
Gửi khung hình nhanh chóng trong khả năng cho phép của API kết xuất đồ hoạ
Phương pháp này gắn một trò chơi với hoạt động của biến SurfaceFlinger và giới thiệu một độ trễ bổ sung. Pipeline (quy trình) hiển thị chứa một hàng đợi khung hình, (kích thước thường là 2 khung hình). Hàng đợi này sẽ được lấp đầy nếu trò chơi đang cố hiển thị khung hình quá nhanh. Không còn chỗ trống trong hàng đợi, vòng lặp trò chơi (hoặc ít nhất là luồng hiển thị) bị một lệnh gọi OpenGL hoặc Vulkan chặn. Sau đó, trò chơi buộc phải đợi phần cứng hiển thị trình chiếu một khung và áp suất ngược này sẽ đồng bộ hoá hai thành phần. Tình trạng này được gọi là nhồi bộ đệm hoặc nhồi hàng đợi. Quá trình kết xuất không nhận biết được sự kiện đang diễn ra, vì vậy, tốc độ khung hình không nhất quán sẽ trở nên tệ hơn. Nếu các mẫu trò chơi nhập dữ liệu trước khung hình, thì độ trễ đầu vào sẽ tệ hơn.
Tự thân sử dụng tính năng Android Choreographer
Các trò chơi cũng sử dụng Android Choreographer để đồng bộ hoá. Thành phần này (có bằng Java từ API 16 và C++ từ API 24) phân phối các tick (kim đánh dấu nhịp độ khung hình) thông thường ở cùng tần số với hệ thống hiển thị phụ. Liên quan đến phần cứng VSYNC thực tế, vẫn có những khác biệt tinh tế nhất định khi tick này được phân phối, và các mức chênh lệch này khác nhau tuỳ theo thiết bị. Việc nhồi bộ đệm vẫn có thể xảy ra đối với các khung hình dài.
Ưu điểm của thư viện Frame Pacing
Thư viện Frame Pacing sử dụng Android Choreographer để hỗ trợ bạn đồng bộ hoá và giải quyết vấn đề không nhất quán trong quá trình phân phối tick. Phương thức này sử dụng dấu thời gian trình bày để đảm bảo khung được trình chiếu vào đúng thời điểm và hàng rào đồng bộ hoá để tránh tình trạng nhồi bộ đệm. Thư viện này sẽ sử dụng NDK Choreographer Trong trường hợp không có sẵn NDK Choreographer, thư viện này sẽ chuyển sang sử dụng Java Choreographer.
Thư viện này xử lý nhiều tốc độ làm mới (nếu được thiết bị hỗ trợ), giúp bạn linh hoạt hơn trong việc trình chiếu khung hình. Ví dụ: đối với một thiết bị có hỗ trợ tốc độ làm mới 60 Hz cũng như 90 Hz, một trò chơi không thể tạo ra 60 FPS có thể giảm xuống còn 45 FPS thay vì 30 FPS để duy trì độ mượt. Thư viện phát hiện tốc độ khung hình dự kiến của trò chơi và tự động điều chỉnh thời gian hiện khung hình cho phù hợp. Thư viện Frame Pacing cũng cải thiện thời lượng pin do tránh được việc các bản cập nhật hiển thị một cách không cần thiết. Ví dụ: nếu một trò chơi đang kết xuất ở tốc độ 60 khung hình/giây nhưng màn hình đang cập nhật ở tốc độ 120 Hz, thì màn hình sẽ được cập nhật 2 lần cho mỗi khung hình. Thư viện Frame Pacing ngăn điều này xảy ra bằng cách đặt tốc độ làm mới thành giá trị được thiết bị hỗ trợ và sát nhất với tốc độ khung hình mục tiêu.
Cách thức hoạt động
Các phần sau đây cho thấy cách thư viện Frame Pacing giải quyết các khung hình dài và ngắn của trò chơi để đạt được tốc độ khung hình chính xác.
Chỉnh tốc độ khung hình ở 30 Hz
Khi kết xuất ở 30 Hz trên thiết bị hỗ trợ 60 Hz, tình huống lý tưởng trên Android được hiển thị trong hình 1. SurfaceFlinger chốt các bộ đệm đồ hoạ mới nếu có (NB trong sơ đồ có nghĩa là "hiện không có bộ đệm" và bộ đệm trước đó được lặp lại).
Hình 1. Tốc độ khung hình lý tưởng ở 30 Hz trên thiết bị hỗ trợ 60 Hz
Khung hình trò chơi ngắn dẫn đến tình trạng giật hình
Trên hầu hết các thiết bị hiện đại, các công cụ phát triển trò chơi sử dụng choreographer của nền tảng cung cấp các tick để tăng số lượt gửi khung hình. Tuy nhiên, vẫn có khả năng tốc độ khung hình kém do khung hình ngắn, như trong hình 2. Người chơi gọi các khung hình ngắn theo sau là các khung hình dài là tình trạng kết xuất gián đoạn.
Hình 2. Khung hình trò chơi ngắn C khiến khung hình B chỉ hiển thị một lần, sau đó là nhiều khung hình C
Thư viện Frame Pacing giải quyết vấn đề này bằng cách sử dụng dấu thời gian trình bày. Thư viện
sử dụng này tiện ích về dấu thời gian trình bày
EGL_ANDROID_presentation_time
và
VK_GOOGLE_display_timing
để khung hình không được trình chiếu sớm, như trong hình 3.
Hình 3. Khung hình B của trò chơi được trình chiếu hai lần để hiển thị mượt mà hơn
Khung hình dài dẫn đến tình trạng giật hình và độ trễ
Khi khối lượng công việc hiển thị mất nhiều thời gian hơn khối lượng công việc của ứng dụng, các khung hình bổ sung sẽ được thêm vào hàng đợi. Điều này một lần nữa, dẫn đến tình trạng giật hình và cũng có thể dẫn đến thời gian chờ của khung hình lâu hơn do bộ nhớ đệm (xem hình 4). Thư viện Frame Pacing giúp loại bỏ cả hiện tượng giật hình và độ trễ.
Hình 4. Khung hình dài B đưa ra tốc độ không chính xác cho 2 khung hình: A và B
Thư viện giải quyết vấn đề này bằng cách sử dụng hàng rào đồng bộ hoá
(EGL_KHR_fence_sync
và
VkFence
)
để chèn các lệnh chờ vào ứng dụng nhằm cho phép quy trình hiển thị bắt kịp, thay vì để cho áp lực ngược tăng lên. Khung hình A vẫn trình chiếu
một khung khác, nhưng khung hình B hiện đã trình chiếu chính xác, như trong hình 5.
Hình 5. Khung hình C và D đang chờ trình chiếu
Các chế độ hoạt động được hỗ trợ
Bạn có thể định cấu hình thư viện Frame Pacing để hoạt động ở một trong ba chế độ sau:
- Chế độ tự động đang tắt + Quy trình (Pipeline)
- Chế độ tự động bật + Quy trình (Pipeline)
- Chế độ tự động bật + Chế độ Quy trình (Pipeline) tự động (Quy trình/Phi quy trình)
Chế độ đề xuất
Bạn có thể thử nghiệm với chế độ tự động và chế độ quy trình, nhưng bạn sẽ bắt đầu bằng cách tắt các chế độ này và đưa vào các chế độ sau đây sau khi khởi chạy Swappy:
swappyAutoSwapInterval(false);
swappyAutoPipelineMode(false);
swappyEnableStats(false);
swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);
Chế độ Quy trình (Pipeline)
Để điều phối khối lượng công việc của engine, thư viện thường sử dụng mô hình pipeline phân tách khối lượng công việc của CPU và GPU trên các ranh giới của VSYNC.
Hình 6. Chế độ Quy trình (Pipeline)
Chế độ Phi quy trình (Non-pipeline)
Nhìn chung, phương pháp này làm cho độ trễ màn hình nhập thấp hơn, dễ dự đoán hơn. Trong trường hợp trò chơi có thời gian kết xuất khung hình quá thấp, cả khối lượng công việc của CPU và GPU đều có thể vừa với một khoảng hoán đổi. Trong trường hợp này, phương pháp phi quy trình sẽ mang lại độ trễ màn hình đầu vào thấp hơn.
Hình 7. Chế độ Phi quy trình (Non-pipeline)
Chế độ tự động
Hầu hết các trò chơi đều không biết cách chọn khoảng hoán đổi, là thời lượng hiển thị từng khung hình (ví dụ: 33,3 mili giây đối với 30 Hz). Trên một số thiết bị, trò chơi có thể hiển thị ở 60 FPS, trong khi trên một thiết bị khác, trò chơi có thể cần giảm xuống giá trị thấp hơn. Chế độ tự động đo lường thời gian của CPU và GPU để thực hiện những việc sau:
- Tự động chọn khoảng hoán đổi: Các trò chơi hiển thị 30 Hz trong một số cảnh và 60 Hz trong các đoạn khác có thể cho phép thư viện điều chỉnh khoảng thời gian này một cách linh động.
- Huỷ tính năng pipeline đối với các khung hình cực nhanh: Mang lại độ trễ màn hình đầu vào tối ưu trong mọi trường hợp.
Nhiều tốc độ làm mới
Các thiết bị hỗ trợ nhiều tốc độ làm mới mang lại tính linh hoạt cao hơn trong việc chọn khoảng hoán đổi có vẻ mượt hơn:
- Trên thiết bị 60 Hz: 60 FPS / 30 FPS / 20FPS
- Trên thiết bị 60 Hz và 90 Hz: 90 FPS / 60 FPS / 45 FPS / 30 FPS
- Trên thiết bị 60 Hz và 90 Hz và 120 Hz: 120 FPS / 90 FPS / 60 FPS / 45 FPS / 40 FPS / 30 FPS
Thư viện chọn tốc độ làm mới phù hợp nhất với thời lượng kết xuất thực tế của khung hình của trò chơi, nhờ đó mang lại trải nghiệm hình ảnh tốt hơn.
Để biết thêm thông tin về nhiều tốc độ khung hình và tốc độ làm mới khác, hãy xem bài đăng về Kết xuất hình ảnh với tốc độ làm mới cao trên Android.
Số liệu thống kê về khung hình
Thư viện Frame Pacing cung cấp những số liệu thống kê sau đây để gỡ lỗi và lập hồ sơ:
- Biểu đồ về số lần màn hình làm mới một khung đã đợi trong hàng đợi của trình kết hợp sau khi hoàn tất quá trình kết xuất.
- Biểu đồ về số lần làm mới màn hình đã chuyển giữa thời gian trình chiếu được yêu cầu và thời gian trình chiếu thực tế.
- Biểu đồ về số lần làm mới màn hình đã chuyển giữa hai khung liên tiếp.
- Biểu đồ về số lần làm mới màn hình đã chuyển giữa thời điểm bắt đầu hoạt động của CPU đối với khung này và thời gian trình chiếu thực tế.
Bước tiếp theo
Hãy xem một trong các hướng dẫn sau để tích hợp thư viện Android Frame Pacing vào trò chơi của bạn:
- Tích hợp Tốc độ khung hình Android (Android Frame Pacing) vào trình kết xuất OpenGL
- Tích hợp Android Frame Pacing vào trình kết xuất Vulkan