Tối Ưu Hóa Bất Biến Và Phép Toán - nói dối e blog

Tối Ưu Hóa Bất Biến Và Phép Toán

Cách đây một năm, nhóm chúng tôi đã tiến hành phân tích hiệu năng trên engine game đang phát triển. Khi số lượng đối tượng trong cảnh tăng cao, chúng tôi nhận thấy có một phép toán chiếm hơn 10% thời gian CPU. Dù nhận định đây là điểm có tiềm năng tối ưu, nhưng do khâu ưu tiên công việc, vấn đề đã bị tạm gác lại.

Cụ thể, hệ thống engine mỗi khung hình đều đưa các đối tượng trong cảnh vào “hàng đợi kết xuất” (render queue). Mỗi đối tượng có thông tin lưới, vật liệu, khối bao bọc AABB (Axis-Aligned Bounding Box) và ma trận không gian thế giới.

Trong hệ thống tài nguyên, các đối tượng cảnh tham chiếu đến những tài nguyên bất biến được chia sẻ bởi nhiều đối tượng. Những tài nguyên này có cấu trúc phân cấp, ví dụ như một mô hình gốc có thể gồm nhiều mô hình con. Hàng đợi kết xuất cuối cùng chỉ chứa thông tin các mô hình con không thể chia nhỏ hơn.

Điều này dẫn đến việc số lượng đối tượng trong hệ thống quản lý cảnh ít hơn nhiều so với số lượng phần tử trong hàng đợi kết xuất. Đây chính là lý do tại sao mỗi khung hình chúng tôi đều xây dựng lại hàng đợi thay vì duy trì một danh sách liên kết bền vững qua các khung hình.

Vấn đề nằm ở phép biến đổi khối bao bọc của mỗi phần tử trong hàng đợi kết xuất. Khối bao này được dùng cho quá trình “culling” (loại bỏ đối tượng ngoài tầm nhìn). Ở mức cơ bản, hệ thống sẽ kiểm tra giao điểm giữa frustum camera và AABB để loại bỏ các đối tượng không cần kết xuất. Trong tương lai, có thể mở rộng thành mô-đun culling phức tạp hơn như middleware Umbra nổi tiếng.

Mặc dù đã cân nhắc giải pháp culling bằng GPU, nhưng vì hạn chế về nhân lực, nhóm vẫn tập trung vào giải pháp CPU. Điều này đòi hỏi mỗi phần tử trong hàng đợi phải được biến đổi AABB từ không gian địa phương sang không gian thế giới thông qua phép toán đơn giản kết hợp ma trận không gian thế giới.

Kết quả phân tích cho thấy dù phép toán này đơn giản, nhưng với số lượng phần tử lớn, thời gian xử lý không còn “nhẹ nhàng” như kỳ vọng. Đặc biệt trong cảnh phức tạp, đa số đối tượng là tĩnh (thay đổi ít giữa các khung hình), việc lặp lại nhiều lần các phép toán tương tự cho thấy rõ tiềm năng tối ưu.

Một phương án trực quan là duy trì hàng đợi kết xuất dưới dạng bền vững, chỉ cập nhật khi có thay đổi. Tuy nhiên, do các mô hình con được tái sử dụng rộng rãi, việc sao chép dữ liệu từ thư viện tài nguyên sẽ gây lãng phí. Vì vậy, tôi ưu tiên giải pháp tối ưu hóa phép toán trùng lặp mà không thay đổi kiến trúc hệ thống hiện tại.

Chúng tôi nhận ra phép toán cho mỗi mô hình con phụ thuộc vào ba yếu tố: (1) ma trận không gian thế giới của đối tượng, (2) ma trận chuyển đổi từ mô hình gốc đến mô hình con trong tài nguyên, và (3) AABB ban đầu của mô hình con. Nếu cả ba yếu tố này không đổi, kết quả phép toán cũng không thay đổi.

Mỗi phép toán có đầu vào gồm 40 số float (16+16+8), việc dùng bộ nhớ đệm với khóa 160 byte khó mang lại hiệu quả. Tuy nhiên, thư viện toán học của chúng tôi đã thiết kế các ma trận/vector là bất biến - mỗi giá trị được đại diện duy nhất qua ID. Nhờ đó, thay vì dùng giá trị thực tế làm khóa cache, chỉ cần dùng ID để tham chiếu, giảm đáng kể kích thước khóa.

Hơn nữa, khi một mô hình được đưa vào hàng đợi kết xuất, tất cả mô hình con của nó thường được kết xuất đồng thời. Vì vậy, cache chỉ cần dùng ID của ma trận không gian thế giới làm khóa chính đủ để xử lý.

Điều đặc biệt là hai đối tượng có cùng giá trị ma trận không gian thế giới nhưng được tính toán qua các đường dẫn khác nhau sẽ có ID khác nhau. Tuy nhiên, các đối tượng tĩnh không đổi giữa các khung hình sẽ được hệ thống quản lý cảnh tối ưu bằng cách tái sử dụng ma trận không gian thế giới đã tính toán, giữ nguyên ID tương ứng.

Về cấu trúc cache, giá trị lưu trữ bao gồm: các ma trận chuyển đổi mô hình con tương ứng, AABB gốc, và AABB đã được tính toán ở khung hình trước. Khi gặp bộ ba yếu tố (ma trận không gian thế giới + ma trận chuyển đổi + AABB) khớp với cache, kết quả có thể được tái sử dụng ngay lập tức mà không cần tính toán lại.

0%