[Unity Series 2] Tối ưu hiệu năng Ứng dụng game Unity - Rendering Pipeline

Ở phần trước, mình đã giới thiệu về các công cụ sử dụng trong Unity để kiểm tra hiệu năng của ứng dụng. Trong phần này, mình sẽ giới thiệu quá trình Rendering và một vài khái niệm của nó.

Sau khi tìm ra được những nguyên nhân gây ra hiện tượng giật lag của ứng dụng, việc tiếp theo chúng ta cần làm là giải quyết từng vấn đề một. Tuy nhiên, để giải quyết được vấn đề, chúng ta cần hiểu CPU và GPU xử lý như thế nào, từ đó tìm ra được vấn đề nằm ở đâu trong quá trình CPU và GPU xử lý. Bài viết được tham khảo từ nguồn Performance Optimization Tutorial của Unity.

optimize-all-things-NEW

Nói một cách đơn giản, quá trình Rendering được thực hiện như sau:

  • Đầu tiên, CPU sẽ xác định những object nào cần được vẽ và vẽ nó như thế nào
  • Sau đó CPU sẽ gửi những mô tả bên trên tới GPU.
  • GPU sẽ vẽ các object theo mô tả của CPU.

Giờ thì các bạn cũng đã hình dung ra CPU và GPU làm việc gì trong quá trình Rendering rồi nhỉ. Tiếp theo chúng ta sẽ đi sâu chi tiết vào từng công việc này: Với mỗi Frame, CPU sẽ thực hiện công việc sau:

  • CPU sẽ duyệt toàn bộ những object có trong Scene để xác định object nào cần được render. Những object không được render được gọi là culled (Chắc các bạn sẽ mường tượng đến tính năng Occlusion Culling nhỉ - tính năng này sẽ được giới thiệu ở phần sau).
  • Sau đó, CPU sẽ thu thập thông tin về các object được render và sắp xếp dữ liệu thu được vào trong các dòng lệnh được gọi là Draw Calls. Một Draw Calls chứa các thông tin về một mesh duy nhất và nó xác định mesh này sẽ được render như thế nào ( ví dụ như những texture nào sẽ được sử dụng). Trong một vài trường hợp, các object có thể chia sẻ những setting có thể được kết hợp vào chung một Draw Call. Kêt hợp dữ liệu của các object khác nhau vào chung một Draw Call được gọi là batching. Với mỗi Draw Call, CPU sẽ tạo một gói dữ liệu được gọi là Batch.

Với mỗi Batch chứa một Draw Call, CPU sẽ tiếp tục thực hiện như sau:

  • CPU sẽ gửi một lệnh tới GPU để thay đổi một số biến được gọi là Render State. Những lệnh này được gọi là SetPass Call. Một Setpass Call sẽ gọi cho GPU những setting nào sẽ được sử dụng cho mesh tiếp theo. Một Setpass Call sẽ chỉ được gửi đi nếu mesh tiếp theo được render yêu cầu một thay đổi trong Render State từ mesh trước đó.
  • Sau đó, CPU sẽ gửi Draw Call tới GPU. Draw Call sẽ chỉ thị GPU render mesh bằng cách sử dụng các setting được xác định trong cuộc gọi SetPass Call gần nhất.
  • Trong một vài trường hợp, có thể cần nhiều hơn một pass có thể được yêu cầu cho Batch. Một Pass là một phần của shader code và một pass mới yêu cầu một sự thay đổi tới Render State. Với mỗi pass trong Batch, CPU sẽ gửi một cuộc gọi SetPass Call mới và sau đó sẽ gửi lại Draw Call.

GPU sẽ xử lý vẽ các object như sau:

  • GPU sẽ xử lý các tác vụ từ CPU theo thứ tự chúng được gửi.
  • Nếu tác vụ hiện tại là một cuộc gọi SetPass Call, GPU sẽ cập nhật lại Render State.
  • Nếu tác vụ hiện tại là một Draw Call, GPU sẽ render Mesh.
  • Quá trình này sẽ lặp lại liên tục cho đến khi tất cả các tác vụ được gửi từ CPU đã được xử lý bởi GPU.

KẾT LUẬN

Điều quan trọng nhất mà chúng ta cần hiểu về rendering là: cả CPU và GPU phải hoàn thành tất cả các tác vụ để render frame. Do vậy, chỉ cần một tác vụ nào đó tốn nhiều thời gian để hoàn thành, nó sẽ là nguyên nhân gây ra delay trong việc render frame -> giảm FPS của ứng dụng. Khi ứng dụng của chúng ta tốn quá nhiều thời gian để render một frame do CPU thực hiện các tác vụ thì được gọi là CPU Bound. Và tương tự với GPU được gọi là GPU Bound. Để tối ưu hiệu năng ứng dụng, trước khi đi vào tối ưu chúng ta cần nắm được ứng dụng của mình đang bị CPU bound hay GPU bound, từ đó tìm ra hướng đi cho việc tối ưu hiệu năng. Trong phần tiếp theo, mình sẽ giới thiệu cho các bạn cách tối ưu hiệu năng khi ứng dụng của bạn bị CPU bound hay GPU bound.

Bài viết liên quan
scrolltop