Giải phẫu một texture: Vì sao bộ nhớ texture phức tạp đến vậy?

Công nghệ16 tháng 9, 2026·6 phút đọc

Bài viết phân tích chi tiết cách một texture được lưu trữ trong bộ nhớ, từ nén khối BC7, thứ tự texel, swizzle Morton, mipmap cho đến hệ thống tile. Đây là góc nhìn thực tế từ một lập trình viên đồ họa đang chuyển engine game PC lên console, nơi mỗi nền tảng có cách bố trí bộ nhớ khác nhau.

Giải phẫu một texture: Vì sao bộ nhớ texture phức tạp đến vậy?

Giải phẫu một texture: Vì sao bộ nhớ texture phức tạp đến vậy?

Khi viết code chuyển đổi dữ liệu texture giữa các nền tảng game, tác giả Daniel "Agentlien" Kvick đã nhận ra mức độ phức tạp thực sự của bộ nhớ texture. Bài viết này giải thích vì sao một hình ảnh đơn giản lại cần đến hàng loạt kỹ thuật tối ưu hóa phức tạp, từ nén khối cho tới swizzle và tile.

Vì sao cần quan tâm đến vị trí bộ nhớ?

Nếu chỉ nhìn vào bề mặt, một texture RGBA 1024x1024 với 32-bit màu có vẻ đơn giản: chỉ là một dòng texel chạy từ trái sang phải, từng hàng một, mỗi texel gồm bốn byte liên tiếp cho bốn kênh màu.

Nhưng thực tế không hề đơn giản như vậy. Trong game hiện đại, texture được tổ chức theo một hệ thống phân cấp phức tạp, và thứ tự sắp xếp các phần tử trong bộ nhớ có thể khác nhau giữa các nền tảng.

Điều này có nghĩa là khi chuyển texture từ nền tảng này sang nền tảng khác, ta phải tính toán lại địa chỉ bộ nhớ cho từng phần tử.

Texture gỗ 1024x1024 dùng làm ví dụTexture gỗ 1024x1024 dùng làm ví dụ

Hãy tưởng tượng nếu ta bỏ qua mọi phức tạp và đọc dữ liệu như một dòng byte thô. Kết quả sẽ là một mớ hỗn độn màu sắc vô nghĩa:

Nén khối BC7: giảm kích thước, tăng tốc độ

Hầu hết texture trong game hiện đại đều dùng nén khối (block compression). Định dạng phổ biến nhất hiện nay là BC7 — một chuẩn nén có tổn hao, hoạt động dựa trên giả định rằng các texel lân cận thường có màu sắc tương tự nhau.

Cách hoạt động của BC7:

  • Dữ liệu được chia thành các khối 4x4 texel
  • Mỗi khối lưu các cặp màu tham chiếu gọi là điểm đầu (end points)
  • Mỗi texel trong khối được biểu diễn bằng một chỉ số trỏ tới giá trị nội suy giữa các điểm đầu
  • Một khối nén chỉ chiếm 16 byte nhưng mô tả tới 16 texel — tức trung bình 1 byte mỗi texel, nén gấp 4 lần

Điều đáng kinh ngạc là dù nén mạnh như vậy, mắt người gần như không thể phân biệt texture BC7 với texture không nén, ngay cả khi đặt cạnh nhau.

Tuy nhiên, nén khối cũng đặt ra thách thức lớn khi debug. Một khối BC7 chứa quá nhiều thông tin đan xen, khiến việc đọc bộ nhớ thô gần như vô nghĩa nếu không có công cụ chuyên dụng. Nếu byte bị lệch một chút, cách đọc điểm đầu và chỉ số sẽ sai hoàn toàn.

Texture bị đảo lộn khi diễn giải sai cấu trúc khốiTexture bị đảo lộn khi diễn giải sai cấu trúc khối

Một mẹo hữu ích: có thể ghi 16 byte toàn số 0 để tạo ra một khối 4x4 đảm bảo hiển thị màu đen. Dùng các khối đen này làm mốc đánh dấu, rồi chèn dữ liệu debug thô vào giữa các mốc — cách này giúp định vị chính xác vị trí bộ nhớ ngay cả khi có lỗi căn chỉnh.

Thứ tự texel và swizzle

Để tăng tính cục bộ bộ nhớ (cache locality), texture không được lưu theo thứ tự hàng-hàng-cột-cột thông thường. Thay vào đó, các texel gần nhau về mặt hình ảnh cần nằm gần nhau trong bộ nhớ, vì chúng thường được lấy mẫu cùng lúc.

Kỹ thuật sắp xếp lại này gọi là swizzle. Nổi tiếng nhất là thứ tự Morton — sắp xếp texel theo mô hình chữ Z fractal thay vì quét từng hàng.

So sánh thứ tự texel tuyến tính và thứ tự MortonSo sánh thứ tự texel tuyến tính và thứ tự Morton

Các nền tảng khác nhau có thể dùng swizzle khác nhau cho cùng một texture. Điều đó có nghĩa là khi chuyển đổi, ta phải sắp xếp lại toàn bộ texel theo đúng thứ tự của nền tảng đích.

Nếu căn chỉnh đúng nhưng dùng sai swizzle, hình ảnh sẽ trông như một trò xếp hình 4x4 bị xáo trộn.

Mipmap: các phiên bản thu nhỏ

Texture thực chất không phải một hình ảnh đơn lẻ. Mỗi texture chứa một chuỗi các phiên bản thu nhỏ dần gọi là mip:

  • Mip 0 có kích thước gốc
  • Mỗi mip tiếp theo có chiều rộng và cao giảm một nửa

Mục đích là để khi render, độ phân giải texel được lấy mẫu khớp với độ phân giải của điểm ảnh đích. Với bề mặt ở xa camera, mỗi pixel sẽ phủ lên nhiều texel — nếu không dùng mip, ta phải lấy mẫu một vùng rộng, rất tốn kém và gây hiện tượng răng cưa, nhấp nháy, hoa văn Moiré.

Các mip được lưu theo thứ tự xác định, nhưng thứ tự tăng dần hay giảm dần lại khác nhau giữa các nền tảng. Vì vậy không thể đơn giản tăng con trỏ nguồn và đích song song — phải xác định vị trí bắt đầu của từng mip trên mỗi nền tảng.

Tile: chia nhỏ để tối ưu

Lớp phức tạp tiếp theo là tile. Bộ nhớ texture được chia thành các tile vuông, mỗi tile thường 64 KiB.

  • Một mip 1024x1024 dùng BC7 gồm 16 tile xếp thành lưới 4x4
  • Mỗi tile chứa vùng 256x256 texel, tương ứng 4096 khối BC7
  • Bên trong mỗi tile, texel vẫn được nén khối và swizzle như mô tả ở trên

Tile còn cho phép game streaming từng phần texture — chỉ nạp phần đang hiển thị của texture lớn.

Các mip nhỏ hơn một tile đầy đủ sẽ được đóng gói (pack) lại với nhau vào càng ít tile càng tốt, gọi là mip tail. Với texture ví dụ, 8 mip cuối cùng có thể nằm gọn trong một tile 64 KiB duy nhất — tiết kiệm bộ nhớ đáng kể.

Vấn đề bộ nhớ không liên tục

Trong engine game hiện đại, texture thường được stream từng tile vào một vùng bộ nhớ chung (gọi là heap trong DirectX 12). Mỗi tile được ánh xạ tới một vùng bộ nhớ riêng.

Khi các mip khác nhau được nạp và giải phóng theo nhu cầu, bộ nhớ texture có thể bị phân mảnh. Điều này dẫn tới hệ quả quan trọng:

  • Các tile của cùng một texture có thể không nằm liền kề nhau trong bộ nhớ
  • Cần một bảng ánh xạ từ chỉ số tile sang địa chỉ cụ thể trong heap
  • Nếu giả định sai rằng bộ nhớ liên tục, ta có thể ghi đè lên tile của texture khác và bỏ sót tile của chính mình

Chính tác giả đã từng mắc lỗi này và mất khá nhiều thời gian mới nhận ra giả định sai lầm của mình.

Tổng kết

Để một texture hiển thị đúng và nhanh, các lập trình viên đồ họa phải xử lý đồng thời nhiều lớp phức tạp:

  • Nén khối giảm kích thước và tăng tốc truyền dữ liệu
  • Swizzle cải thiện tính cục bộ bộ nhớ
  • Mipmap giảm chi phí render và chống răng cưa
  • Tile cho phép streaming và đóng gói bộ nhớ hiệu quả
  • Ánh xạ bộ nhớ không liên tục xử lý phân mảnh heap

Mỗi nền tảng có thể có quy tắc riêng cho từng lớp, khiến việc chuyển đổi texture trở thành một bài toán phức tạp hơn nhiều so với vẻ ngoài của nó. Nhưng chính những chi tiết tưởng chừng nhỏ nhặt này lại là thứ giúp game của bạn chạy nhanh hơn một chút — và đó là lý do chúng đáng được tìm hiểu.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗