Phuongne, Th3 07, 2020
Load dữ liệu vào RAM & VRAM
RAM: bộ nhớ tạm thời cạnh CPU, truy cập rất nhanh
VRAM: bộ nhớ tạm thời cạnh GPU, truy cập rất nhanh
Đầu tiên các dữ liệu cần vẽ (3D) như vertex, texture, meshes… sẽ được load từ disk (SSD hay HDD) sang system memory (RAM) để CPU có thể truy cập nhanh hơn, sau đó lại được load từ RAM sang bộ truy cập nhanh của GPU là VRAM, Bởi GPU truy cập dữ liệu ở VRAM nhanh hơn so với dữ liệu ở disk và RAM.
Sau khi đã load dữ liệu về texture vào VRAM, nếu CPU không cần sử dụng dữ liệu đó thì nó sẽ bị xoá đi và phải chắc chắn rằng dữ liệu sẽ không được CPU sử dụng lại trong một thời gian sắp tới, bởi việc load từ disk sang RAM cũng ngốn kha khá performance.
Còn dữ liệu về meshes thường lưu trữ lâu hơn trong RAM nếu đang còn sử dụng tới để phát hiện, xử lý va chạm…
“Nếu GPU xử lý nhanh và thời gian truy cập VRAM chậm thì sao?”
Dữ liệu đã sẵn sàng ở trong VRAM, nếu thời gian GPU truy cập VRAM không đủ nhanh, thì GPU còn có một bộ nhớ nhỏ đặt trực tiếp trong nó nữa gọi là L2 Cache, keyword của nó là “on-chip caches”. Thậm chí nếu vẫn không đủ nhanh thì GPU có thể truy cập vào một bộ nhớ nằm sát ngay cạnh các cores của GPU là L1 Cache
Tuy nhiên size của 2 cái bộ nhớ trên rất nhỏ, ví dụ L2 Cache của NVIDIA là GM204 chỉ có 2048 KB, L1 Cache như GM204 chỉ có 384 KB
“Còn bộ nhớ nào GPU sử dụng nữa không?”
Thật ra chúng ta còn bộ nhớ GPU dùng để lấy dữ liệu và xuất dữ liệu đã xử lý gọi là Register, GPU sẽ lấy dữ liệu từ Register, xử lý, rồi trả kết quả lại cho Register. Developer thường không cần quan tâm tới các bộ nhớ này.
“Vậy làm thế nào GPU có thể vẽ các dữ liệu đã nhận được từ CPU lên màn hình?”
Sau khi GPU đã có vertex, meshes hay texture trong tay, CPU sẽ định nghĩa một loạt các thông tin cần phải xử lý cho meshes gọi là Render State như ánh sáng như nào, độ trong suốt như nào, vertex nằm ở đâu,… vân vân và mây mây
Render State và SetPass Call
Render State là các định nghĩa thông tin về cách hiển thị một meshes như mình nói ở trên: lighting (ánh sáng), vertex (đỉnh), material, lighting, transparency (độ trong suốt),… Các vật thể mà CPU nhờ GPU render đều sẽ dựa trên các thông tin trên, có thể nhiều meshes cùng chia sẻ một setting với nhau, kỹ thuật kết hợp vẽ các meshes với cùng một setting hay một RenderState nhằm tối ưu thời gian gọi là batching.
Khi đã có Render State, CPU sẽ gọi tới GPU cập nhật setting bằng lệnh SetPass Call (cách gọi khác là RenderState Change, RenderState Call,…), sau đó nhờ GPU vẽ một mesh nào đó bằng một lệnh gọi là Draw Call.
Ví dụ thứ tự như này: SetPass Call: Đỏ -> Draw Call: Hình tam giác -> Draw Call: Hình chữ nhật -> SetPass Call: vàng -> Draw Call: Hình vuông ->…
Thì hình tam giác và hình chữ nhật chung setting là đỏ, hình vuông có setting là vàng, kỹ thuật gộp setting hình tam giác và hình chữ nhật là batching.
Draw Call
Một Draw Call không gì khác hơn là một lệnh của CPU gửi tới GPU nhờ vẽ MỘT mesh, Draw Call chỉ chứa một dữ liệu duy nhất là mesh cần vẽ, không chứa bất kỳ một thông tin gì về vertex, texture, color, shader,… gì cả, GPU sẽ vẽ mesh đó bằng setting (Render State) của SetPass Call gần nhất (xem lại ví dụ ở Render State). Tất nhiên là mesh cần vẽ đã nằm trong VRAM (hoặc bộ nhớ hỗ trợ khác)
Sau khi nhận lệnh từ CPU, GPU sẽ bắt đầu vẽ một mesh lên màn hình dựa trên các thông tin trong Render State cũng như dữ liệu trong VRAM, quá trình vẽ lên bao gồm một loạt các sự kiện diễn ra liên tục, quá trình này được gọi là Render Pipeline.
Render Pipeline
Sau khi GPU đã có trong tay các thông tin cần có bao gồm một số dữ liệu như: vertices (các đỉnh), textures,…
GPU sẽ phải tạo một mesh (khối) dựa trên các vertices (đỉnh) nhận được bằng các triangle (tam giác = 3 đỉnh), định nghĩa ánh sáng cho mesh hay cách mesh phản ứng với ánh sáng, vẽ mesh (khối) từ không gian 3D sang không gian 2D (màn hình)…
Tất nhiên công việc của GPU không đơn giản như vậy mà bao gồm rất nhiều task nhỏ, cùng rất rất nhiều các tính toán cho vertices và pixels. Ngày nay, GPU rất phát triển vì vậy các công việc này thường được xử lý một cách song song.
Thông thường, CPU hiện nay có 6-8 cores, trong khi đó GPU lại có từ hàng trăm đến hàng ngàn cores (không phức tạp bằng cpu-core), sau đây là một vài thông tin.
- GTX 1050 có 640 cores trong khi GTX-1050Ti là 768 cores
- GTX 1060 3GB: 1152 cores và 1280 cores cho phiên bản 6GB
- GTX 1070 có 1920 cores
- GTX 1080 có 2560 cores (1080 Ti là 3584 cores)
“Thế trong lúc GPU đang xử lý thì CPU ngồi chơi hả?”
CPU và GPU sẽ xử lý song song và giao tiếp với nhau thông qua một danh sách (list) gọi là Command Buffer theo nguyên tắc FIFO – First In First Out, giống với cấu trúc Queue.
CPU sẽ đưa vào lệnh, GPU nhặt lệnh và thực hiện, chờ lệnh tiếp theo hoặc lấy lệnh tiếp theo có trong buffer.
Trong trường hợp CPU xử lý quá chậm, GPU lại quá nhanh sẽ gây ra tình trạng bottle-neck (nghẽn cổ chai) mà rất nhiều player hay gamer gặp phải.
Command Buffer
Buffer này giúp CPU và GPU giao tiếp với nhau và hoạt động một cách song song, nếu CPU muốn thay đổi RenderState bằng lệnh SetPass Call hoặc vẽ mesh bằng lệnh DrawCall thì CPU sẽ đẩy vào trong Command Buffer
Sau khi xử lý xong công việc hiện tại, GPU lấy lệnh tiếp theo (theo nguyên tắc FIFO) trong buffer (nếu có) và thực thi.
Trong Unity3D, module UnityEngine.Rendering cũng có một class CommandBuffer hoạt động tương tự, chứa danh sách các lệnh mà graphics cần thực thi, tham khảo ở CommandBuffer
Claims
Bài viết được đăng vào 2020, chỉ mang tính chất tham khảo các bạn nhé
Tạm kết và nguồn tham khảo
Nguồn tham khảo:
https://simonschreibt.de/gat/renderhell-book1/
https://simonschreibt.de/