- Một lập trình viên đã có kinh nghiệm OpenGL lần đầu học Vulkan và chỉ sau khoảng 3 tháng đã tạo được hai demo game nhỏ cùng engine EDBR có thể tái sử dụng
- Thay vì thiết kế một engine đa dụng ngay từ đầu, anh tạo game nhỏ trước rồi chỉ engine hóa những phần cần thiết, nhờ đó giảm thiết kế quá mức và bike-shedding
- Engine có quy mô 19k LoC, gồm 6,7k LoC mã đồ họa và 2k LoC phần trừu tượng Vulkan nhẹ, bao gồm compute skinning, CSM, PBR shading, MSAA, post FX và render UI
vk-bootstrap, Vulkan Memory Allocator,volk,VK_KHR_dynamic_rendering, push constants, buffer device address và bindless descriptor giúp giảm boilerplate Vulkan cũng như việc dùng descriptor set- Vulkan mang lại việc loại bỏ global state, lỗi validation tốt hơn, gỡ lỗi shader bằng RenderDoc, tính nhất quán giữa GPU và OS, nhưng đồng bộ tường minh vẫn phải tự xử lý
Engine EDBR dựa trên Vulkan được tạo trong 3 tháng
- EDBR(Elias Daler’s Bikeshed Engine) bắt đầu từ một dự án học Vulkan và về sau phát triển thành một engine nhỏ có thể tái sử dụng cho các dự án sau
- Mã nguồn engine và game được công khai trên kho GitHub
- Quy mô mã tại thời điểm viết như sau
- Bản thân engine: 19k LoC
- Mã liên quan đến đồ họa: 6,7k LoC
- Lớp trừu tượng Vulkan nhẹ: 2k LoC
- Game mèo 3D: 4,6k LoC
- Game platformer 2D: 1,2k LoC
- Bản thân engine: 19k LoC
- Một số mã không thuộc đồ họa như xử lý input và hệ thống audio được lấy từ engine trước, còn phần đồ họa và nhiều hệ thống cốt lõi được viết mới
- Tác giả đánh giá rằng viết mới phù hợp hơn là cố nhét Vulkan vào lớp trừu tượng OpenGL hiện có
Thứ tự học từ lập trình đồ họa đến Vulkan
- Nếu mới bắt đầu lập trình đồ họa, nên bắt đầu với OpenGL thay vì Vulkan để ít bị choáng ngợp bởi độ phức tạp
- Mục tiêu tối thiểu được khuyến nghị là hiển thị một model có texture lên màn hình và triển khai ánh sáng Blinn-Phong đơn giản
- Shadow mapping cơ bản giúp học cách render scene từ một góc nhìn khác tới một render target khác và sample depth texture
- Các tài liệu học OpenGL được khuyến nghị gồm
- Khi học Vulkan, vkguide là tài liệu hữu ích nhất; nếu mới bắt đầu thì nên đi theo toàn bộ, nhưng với game đơn giản có thể chưa cần ngay mức độ phức tạp như “GPU driven rendering”
- Vulkan Lecture Series by TU Wien trình bày các kiến thức nền tảng về Vulkan, đặc biệt bài giảng về đồng bộ rất hữu ích
- Kết quả học trong tháng đầu tiên là triển khai các tính năng sau
- Tải model glTF
- Compute skinning
- Frustum culling
- Shadow mapping và cascaded shadow maps
Lý do chọn Vulkan và so sánh với WebGPU
- Mục tiêu là các game 3D nhỏ cho desktop, chủ yếu trên Windows và Linux; do ưu tiên công nghệ mã nguồn mở và tiêu chuẩn mở nên lựa chọn giữa OpenGL và Vulkan
- OpenGL đủ cho game nhỏ, nhưng khả năng có phiên bản mới là thấp và trên macOS đang ở trạng thái deprecated, nên tương lai được xem là không chắc chắn
- WebGPU cũng đã được học một phần nhưng có các hạn chế sau
- Chưa ổn định và chưa có nhiều tutorial, ví dụ
- Cú pháp WGSL không được ưa thích bằng GLSL
- Trên desktop, nó gần giống một wrapper nằm trên DirectX, Vulkan, Metal, khiến bản capture RenderDoc khác nhau theo nền tảng và lời gọi WebGPU không tương ứng 1:1 với lời gọi native API
- Không có bindless textures và push constants
- WebGPU cũng có các ưu điểm rõ ràng
- Lỗi validation tốt hơn OpenGL/WebGL và không có global state
- Có những điểm tương đồng với Vulkan nên hữu ích trước khi học Vulkan
- Cần ít boilerplate hơn Vulkan để hiển thị thứ gì đó lên màn hình
- Không cần tự xử lý đồng bộ tường minh
- Có thể chạy game trong trình duyệt
Luồng render một frame
- Một frame được chia thành nhiều bước, mỗi bước được triển khai dưới dạng pipeline hoặc pass
- Ở bước skinning, các model có skeletal animation được xử lý bằng compute shader
- Input là mesh chưa được skinning và joint matrices
- Output là vertex buffer dùng ở các bước render sau
- Ở các bước tiếp theo, static mesh và skinned mesh có thể được xử lý tương tự nhau
- Bước CSM thực hiện cascaded shadow mapping bằng depth texture 4096x4096 và 3 slice
- Ở bước geometry + shading, model được vẽ và shading bằng cách dùng shadow map cùng thông tin light
- Mô hình PBR gần như giống phương pháp được mô tả trong Physically Based Rendering in Filament
- Fragment shader thực hiện toàn bộ tính toán light ảnh hưởng đến mesh đó trong một draw call
- Mọi thứ được vẽ vào multi-sampled texture rồi được resolve
- Depth resolve được xử lý thủ công bằng fragment shader
- Duyệt qua tất cả fragment của multi-sample depth texture và ghi giá trị nhỏ nhất vào non-MS depth texture
- Bước post FX hiện chỉ áp dụng depth fog; về sau tone mapping và bloom cũng dự kiến được xử lý ở bước này
- Ở bước UI, dialogue UI được vẽ và xử lý bằng một draw call
Các thư viện giúp giảm boilerplate Vulkan
vk-bootstrapgiảm boilerplate khởi tạo Vulkan như chọn physical device, tạo swapchain- Nó không phải wrapper cho toàn bộ hàm Vulkan mà chủ yếu tác động đến giai đoạn khởi tạo
- Vulkan Memory Allocator giúp không phải trực tiếp xử lý cấp phát bộ nhớ Vulkan
volkđơn giản hóa việc load extension function- Ví dụ có thể dùng các extension function như
vkSetDebugUtilsObjectNameEXTmà không cần tự lưu chúng dưới dạng con trỏ
- Ví dụ có thể dùng các extension function như
- Lớp
GfxDevicegom các chức năng và đối tượng Vulkan thường dùng- Khởi tạo Vulkan context
- Tạo và quản lý swapchain
beginFramevàendFrame- Tạo image và tải texture
- Tạo buffer
- Quản lý bindless descriptor set
GfxDevice.cpptại thời điểm viết có 714 dòng; việc truyền một đối tượng duy nhất thuận tiện hơn so với truyềnVkDevice,VkQueue,VmaAllocator... tới nhiều nơi
Xây dựng shader và chiến lược tránh descriptor set
- Chọn GLSL làm ngôn ngữ shader do đã có kinh nghiệm với OpenGL
- Shader được biên dịch sang SPIR-V ở giai đoạn build, không phải lúc runtime
- Mã tải shader lúc runtime trở nên đơn giản hơn
- Không có phụ thuộc vào shader compiler lúc runtime
- Phát hiện lỗi shader ngay ở giai đoạn build
glslccó thể chỉ địnhDEPFILEcủa CMake, nên khi shader include thay đổi, các file liên quan có thể được tự động biên dịch lại- Trong Vulkan, uniform phải được gom thành descriptor set, khiến việc truyền dữ liệu phức tạp hơn OpenGL
- Trong phần triển khai, việc dùng descriptor set được giảm mạnh
- Chỉ dùng một descriptor set toàn cục cho bindless texture và sampler
- Phần còn lại chủ yếu được truyền bằng push constants
- Dùng buffer device address để truyền địa chỉ buffer qua push constants
Pipeline class và dynamic rendering
- Các bước rendering được tách thành pipeline class như
PostFXPipeline - Mỗi pipeline thường có các vai trò sau
init: tải shader, khởi tạoVkPipeline,VkPipelineLayoutcleanup: dọn dẹp pipeline và layoutdraw: nhận input cần thiết mỗi frame và thực hiện draw call
- Giả định rằng
drawđược gọi giữavkCmdBeginRenderingvàvkCmdEndRendering - Pipeline bên trong không quan tâm render pass render vào texture nào; bên gọi quyết định render target
- Sử dụng
VK_KHR_dynamic_renderingtrên toàn bộ hệ thống, không dùng Vulkan render pass và subpass- Có nghe nói render pass và subpass hiệu quả hơn trên GPU tile-based, nhưng hiện không cân nhắc hỗ trợ mobile
- Dynamic rendering giúp việc triển khai dễ hơn rất nhiều
Sử dụng PVP, BDA, bindless descriptor
- Dùng một kiểu vertex duy nhất cho mọi mesh
- Khi dùng programmable vertex pulling, có thể tránh phải định nghĩa vertex format như VAO của OpenGL hoặc
VkVertexInputBindingDescription,VkVertexInputAttributeDescriptioncủa Vulkan - Khi dùng buffer device address, có thể truyền địa chỉ buffer qua push constants mà không cần bind vertex buffer vào descriptor set
- Dùng layout
scalarcho push constants và buffer- Dễ xử lý căn chỉnh hơn
std430, có thể thao tác gần như tương tự struct C++ - Giảm nhu cầu thêm member padding trong struct C++
- Dễ xử lý căn chỉnh hơn
- Bindless descriptor được dùng theo cách đặt mảng texture và sampler trong một descriptor set lớn
- Khi texture mới được tải, đưa vào mảng
texturesvà dùng index tương ứng làm bindless texture id - Truyền texture id vào shader bằng push constants
- Khi texture mới được tải, đưa vào mảng
- Sampler được tách khỏi image; tạo common sampler lúc startup và đưa vào mảng
samplers - Material buffer cũng dùng bindless texture id
- Chỉ truyền material ID bằng push constants, rồi fragment shader tra cứu material buffer
- Có thể truy cập texture bằng một số nguyên cho mỗi material mà không cần descriptor set cồng kềnh
- Khuyến nghị tham khảo Vulkan Bindless Texture cho nội dung liên quan đến bindless texture
Dữ liệu động được upload mỗi frame
- Với dữ liệu cần đưa từ CPU lên GPU mỗi frame, dùng cách cấp phát trước một mảng lớn và mỗi frame điền từ index 0
- Ví dụ, lưu toàn bộ joint matrix trong một mảng
mat4lớn, rồi truyền index bắt đầu cho từng skinned mesh bằng push constants - Có hai cách
- Đặt N buffer trên GPU và thay đổi theo từng frame-in-flight
- Chỉ đặt một buffer trên GPU và đặt N staging buffer phía CPU
- Trong hầu hết trường hợp, cách thứ nhất được khuyến nghị
- Tốn thêm bộ nhớ GPU nhưng không cần đồng bộ thủ công
- Nếu cần tiết kiệm bộ nhớ GPU, cách thứ hai có thể hữu ích
- Chưa thấy khác biệt hiệu năng đáng kể giữa hai cách, nhưng nếu upload dữ liệu rất lớn mỗi frame thì có thể phát sinh khác biệt
Cleanup và đồng bộ hóa
- Mẫu deletion queue của vkguide không mang lại nhiều lợi ích trong engine của tác giả
- Vì không cấp phát hoặc hủy đối tượng Vulkan mới mỗi frame
- Cleanup dựa trên destructor của C++ cũng không tiện
- Cần wrapper class, move constructor, move assignment, làm tăng độ phức tạp
- Có nguy cơ vô tình xóa đối tượng đang được dùng giữa frame do wrapper bị hủy
- Hiện tại gọi rõ ràng hàm
cleanupđể dọn dẹp các đối tượng Vulkan ở một nơi- Dễ quên gọi, nhưng khi thoát, Vulkan validation error và VMA assert sẽ báo các cleanup bị thiếu
- Synchronization trong Vulkan khó và phải được quản lý tường minh
- OpenGL và WebGPU xử lý thay phần đồng bộ cần thiết khi đọc texture hoặc buffer
- Trong Vulkan, phải tự đặt barrier để tránh data race
- Hiện tại chia draw thành pass và pipeline, rồi chèn barrier thủ công giữa chúng
- Ví dụ, sau khi skinning pass của compute shader ghi vertex data, chèn barrier trước khi shadow mapping pass đọc dữ liệu đó
- Có thể tự động hóa bằng render graph, nhưng hiện tại hài lòng với đồng bộ thủ công
- Synchronization validation layer của
vkconfiggiúp tìm lỗi đồng bộ
Sprite, skinning, tách game/renderer
- Khi dùng bindless texture, dễ vẽ nhiều sprite trong một draw call mà không cần vertex buffer
- Sprite vertex shader tạo vertex coordinate và UV của quad bằng
gl_VertexIndex - Tất cả sprite draw call được gom vào
SpriteDrawBufferdưới dạngSpriteDrawCommand- transform
- phạm vi UV
- color
- texture ID
- shader ID
- Draw call thực tế có dạng
vkCmdDraw(cmd, 6, spriteDrawCommands.size(), 0, 0)- 6 vertex cho mỗi sprite
- Số instance bằng số sprite
- Sprite renderer có thể vẽ 10.000 sprite trong 315 micro giây
- Compute skinning nhận input vertex và joint matrices từ mesh có skeletal animation để tạo skinned vertex buffer
- Ngay cả 3 con mèo có cùng mesh cũng có thể có animation khác nhau
- Cần output vertex buffer cho từng mesh instance
- Game logic và renderer được tách bằng draw command
- Game logic dùng entt
- Renderer không biết entity hay game object, chỉ xử lý light, scene parameter và mesh draw command
MeshDrawCommandbao gồmmeshId, ma trận transform, bounding sphere, con trỏ skinned mesh, index bắt đầu của joint matrix và việc có cast shadow hay không
tải scene, UI, Dear ImGui
- Không tự xây dựng level editor mà dùng Blender và export sang glTF
- Việc tự viết level editor có thể mất vài tháng hoặc vài năm, nên cách này giúp tiết kiệm thời gian
- Chỉ định việc tạo prefab và physics shape bằng tên node
- Ví dụ: trong
Interact.Sphere.Diary,Interacttrước dấu chấm đầu tiên là tên prefab Spheređược dùng khi tạo sphere physics body trong physics system- Cũng có thể dùng
Capsule,Box; nếu không có thì tạo physics shape từ các vertex của mesh
- Ví dụ: trong
- Các model phức tạp không được đưa trực tiếp vào level glTF, mà được đặt bằng object
Empty->Arrowsrồi đặt tên nhưCat.NearStore- Tạo prefab
Catvà gắn tagNearStore
- Tạo prefab
- Prefab được viết bằng JSON và có thể chứa glTF bên ngoài cùng thông tin movement, physics
- Hệ thống UI lấy cảm hứng từ Roblox UI API
- origin
- relative size
- relative position
offsetPosition,offsetSize- fixed size
- size dựa trên nội dung label/image
- Sau khi tính đệ quy size của UI element, tính position rồi vẽ theo thứ tự từ parent đến children
- Dear ImGui được dùng cho công cụ phát triển và debug
- Do Dear ImGui hiển thị sai trên sRGB framebuffer, tác giả đã viết backend Dear ImGui riêng
- Chỉ viết phần rendering; các logic/OS interaction như xử lý input event và clipboard do backend SDL mặc định của Dear ImGui xử lý
- Ưu điểm của backend tự viết như sau
- Hỗ trợ bindless texture id, có thể vẽ image bằng
ImGui::Image(bindlessTextureId, ...) - Có thể vẽ đúng linear image và non-linear image bằng cách truyền format
- Có thể khởi tạo và xử lý theo cùng cách với các phần mã Vulkan khác của engine
- Hỗ trợ bindless texture id, có thể vẽ image bằng
Thư viện đã dùng và hiệu quả khi chuyển sang Vulkan
- Phần physics dùng Jolt Physics
- Chủ yếu dùng cho collision resolution và basic character movement
JPH::CharacterVirtualxử lý tốt character movement cơ bản
- ECS dùng entt
- Dùng thư viện bên ngoài thay vì ECS tự viết để giảm lượng code cần bảo trì
- Audio dùng openal-soft, libogg, libvorbis
- Profiling dùng Tracy
- Giúp kiểm tra đoạn code nào thực sự tốn ít thời gian đến mức nào, tránh bike-shedding không cần thiết
- Những điểm đạt được khi chuyển sang Vulkan như sau
- Không còn global state của OpenGL, giúp việc trừu tượng hóa dễ hơn
- Ít cần các kiểu OpenGL như
shader.bind(), state tracker hay magic RAII hơn - Validation error phong phú hơn OpenGL
- Có thể debug trực tiếp vertex shader và fragment shader trong RenderDoc
- Khác biệt hành vi giữa GPU và OS ít nổi bật hơn so với OpenGL
- Có thể khám phá các shading language khác như Slang, Shady trong tương lai
- Có nhiều quyền kiểm soát hơn đối với từng khía cạnh của graphics pipeline
Công việc sắp tới
- Các việc dự định làm tiếp theo như sau
- hỗ trợ sign-distance field font
- tải nhiều image và tạo mipmap song song
- bloom
- volumetric fog
- animation blending
- render graph
- ambient occlusion
- hoàn thiện game
- Việc học Vulkan khó, nhưng không khó như dự đoán, và trở thành cơ hội để hiểu sâu hơn về lập trình đồ họa và modern API
1 bình luận
Các ý kiến trên Hacker News
Chủ nghĩa tối giản rất hiệu quả
Tôi thì đi theo hướng ngược lại, và vì thế đang chịu khổ rất nhiều. Tôi đang làm một client metaverse bằng Rust, và ngay lúc này trên màn hình khác vẫn đang hiển thị cảnh avatar đi tàu điện qua một thành phố steampunk khổng lồ. Trước khi ra bản prerelease mới, tôi cho nó chạy thử 12 tiếng mỗi lần
Tôi đặt WGPU và Rend3 lên trên Vulkan; Rend3 cung cấp một API gọn gàng: tạo mesh, texture 2D và object, rồi object tham chiếu tới mesh và texture và xuất hiện trên màn hình. Cơ chế đếm tham chiếu của Rust giữ các liên kết khớp nhau, nên viết khá trực quan
Nhưng khi có nhiều tầng thì vấn đề nảy sinh. WGPU cố hỗ trợ trình duyệt web, Vulkan, Metal, DX11 (gần đây đã bị loại bỏ), DX12, Android, cả OpenGL nữa, nên cần một đội phát triển lớn và khó thay đổi. Bản thân API của WGPU nhìn chung cũng giống Vulkan, nên bạn phải tự lo cấp phát bộ nhớ GPU và đồng bộ hóa
WGPU có vấn đề mẫu số chung thấp nhất. Một số nền tảng không hỗ trợ được một số tính năng nhất định, và WGPU không thể xử lý việc nhiều thread cùng cập nhật bộ nhớ GPU như Vulkan hỗ trợ mà không gây can thiệp lẫn nhau. Các game hoặc client xử lý thế giới lớn cần tính năng này để đưa nội dung lên GPU mà không giết chết frame rate. Mỗi nền tảng có ràng buộc đồng thời khác nhau, nên xung đột khóa cũng có thể làm hiệu năng sụt giảm mạnh
Rend3 đã cố đóng vai trò như lớp mã keo vừa đủ để xử lý đồng bộ hóa và cấp phát, nhưng đặc biệt là đồng bộ hóa thì khó giải quyết theo cách tổng quát. Frustum culling đem lại lợi ích hiệu năng lớn, nhưng occlusion culling lại lỗ vì chi phí tính toán. Xử lý bán trong suốt cũng đau đầu vì cần sắp xếp theo độ sâu. Vì tôi xử lý một thế giới có nhiều cửa sổ, các object bán trong suốt cho phép nhìn ra ngoài và nhìn vào trong là thứ bắt buộc phải có
Có vẻ những người trong Rust 3D stack đã phát bực vì suốt 3 năm tôi liên tục gây áp lực đòi sửa stack. Tất cả đều là tình nguyện viên, còn Vulkan được duy trì vì có tiền và cộng đồng người dùng. Rend3 gần đây bị tác giả bỏ cuộc, nên giờ tôi phải tự chui vào bên trong để sửa. Rất hiếm người làm thứ phức tạp bằng WGPU; phần lớn là game 2D mà Flash cũng làm được, hoặc các cảnh 3D tĩnh đơn giản. Các dự án thương mại vẫn tiếp tục dùng Unity hoặc UE5
Nếu đi thẳng với Vulkan thì cũng phải tự viết đồng bộ hóa, cấp phát, frustum culling và xử lý bán trong suốt, nên đó là một bước chuyển lớn
Nói thêm, Vulkano, wrapper trên Vulkan và Metal, cũng có vấn đề mẫu số chung thấp nhất. Cả Vulkan lẫn Metal đều hỗ trợ cập nhật đồng thời tài sản GPU, nhưng Vulkano thì không cho phép. Tất nhiên Apple lại xử lý theo cách khác
https://gpuweb.github.io/gpuweb/explainer/#multithreading
https://github.com/gpuweb/gpuweb/issues/354
OpenGL cũng chưa từng hỗ trợ thread, nên dùng OpenGL thì không thể như vậy được
Hơn một năm trước tôi từng thử học Vulkan, nhưng giờ không muốn đụng tới nó nữa. Việc bỏ OpenGL và thay bằng thứ khiến cả những việc đơn giản cũng trở nên khó đến vô lý thật sự rất khó chịu. Ví dụ chỉ một khối lập phương xoay thôi cũng cần đến hàng trăm dòng mã
OpenGL cũng không dễ, nhưng người bình thường vẫn có thể học các nền tảng trong thời gian tương đối ngắn. Mua một cuốn nhập môn lập trình đồ họa ở nhà sách lớn, dành một hai buổi chiều là đã có thể dựng được phần render cơ bản. Vulkan có thể tốt hơn ở một số mặt, nhưng kỳ vọng học nhanh nó là không thực tế
Hãy tưởng tượng các chip Intel/ARM/AMD đời mới ra mắt mà không cho dùng C hay C++, rồi nói “chúng tôi ngừng hỗ trợ ngôn ngữ bậc cao, từ giờ hãy chỉ dùng assembly; bạn có nhiều quyền kiểm soát hơn nên sẽ nhanh hơn”. Dĩ nhiên điều đó sẽ bị xem là vô lý
Người ta thường nói Vulkan gần với một “GPU API” hơn là một API đồ họa bậc cao. Nhìn như vậy thì độ phức tạp không có gì đáng ngạc nhiên; bản thân miền này đã khó
Thay vì ví von với việc bỏ ngôn ngữ bậc cao trên CPU hiện đại và bắt chỉ dùng assembly, nó gần với khác biệt giữa C/C++ đơn luồng và C/C++ đa luồng hơn. Độ phức tạp tăng mạnh, không hiểu thì dễ nổ hoặc hiệu năng còn tệ hơn, nhưng đó cũng là con đường thực dụng để tiến lên
OpenGL nhìn chung có thể được triển khai phía trên Vulkan. Thật tiếc là chuẩn OpenGL không còn được phát triển tích cực nữa, nhưng không có gì là vĩnh viễn
Vấn đề là OpenGL không còn khớp tốt với phần cứng hiện nay, nên nếu dùng theo cách ngây thơ thì hiệu năng rất kém hiệu quả. Ngay cả trong OpenGL, nếu đi đến các kỹ thuật loại bỏ gánh nặng cho driver thì Vulkan cũng không khó hơn đến mức đó
Các chip Intel/ARM/AMD hiện nay cũng không hỗ trợ trực tiếp C hay C++. Ta vốn đã dùng assembly, hoặc dùng công cụ bên thứ ba chuyển đổi từ C/C++. Mục tiêu của Vulkan cũng là cung cấp một giao diện chuẩn cấp thấp cho GPU, rồi đặt lên trên đó các abstraction dễ dùng hơn
https://github.com/google/angle
Nhiều điện thoại hiện nay tích hợp ANGLE như hỗ trợ OpenGL duy nhất chạy trên driver Vulkan
Nếu muốn một API hiện đại tương đối dễ dùng và có tính di động, có thể dùng WebGPU qua wgpu của Rust hoặc dawn của C++
Tôi nghĩ Vulkan rất tuyệt, nhưng mục đích của nó là tận dụng tối đa các tính năng GPU cao cấp. Khi dùng tính năng GPU cao cấp, nó cũng có thể cho hiệu năng tốt hơn OpenGL
Nếu không nhắm đến các kỹ thuật render cao cấp, thường tôi cảm thấy OpenGL là con đường được khuyến nghị
Hiện vẫn có nhiều game 2D, low-poly, đồ họa kiểu PS1, và những game như vậy không cần dùng Vulkan
Vulkan là một ví dụ cho thấy ngành game AAA đã nghiêng về chất lượng render và hình thức bên ngoài. Các studio AAA biện minh cho ngân sách bằng engine và nội dung rất cao cấp, nhưng thị trường game 2D/low-poly đang lớn lên khi họ nhận ra người chơi đã mệt mỏi và muốn gameplay hơn là đồ họa
Nếu là nhà phát triển game, hẳn bạn sẽ muốn tập trung vào gameplay và tính năng hơn là chất lượng render
Nó không có trạng thái toàn cục, có thể chọn GPU sẽ dùng lúc runtime, còn xử lý lỗi của OpenGL thì tệ hại. Vulkan có validation layers, artwork chính thức cũng đẹp, tài liệu thì xuất sắc. Việc tải dữ liệu lên GPU bất đồng bộ từ luồng thứ hai cũng hợp lý hơn, và còn có các tính năng GPU cao cấp như mesh shader hay RTX
Người tiêu dùng có từ chối game indie có đồ họa tầm AAA không? Có lẽ không. Lý do những game như vậy ít là vì chúng khó khả thi về tài chính, và có một thị trường đủ lớn chấp nhận đồ họa cách điệu hơn, độ trung thực thấp hơn
Đồng thời, nó cho phép truy cập các tính năng hiện đại như compute shader mà WebGL không dùng được, và cũng không có nhiều di sản chồng chất với nhiều cách làm cùng một việc như OpenGL. Ưu điểm chính là nó mới, nhưng vì thế tutorial ít hơn rất nhiều, và đây là một nhược điểm khá nghiêm trọng
Framework và engine thì nhiều vô tận nhưng phần lớn chưa hoàn chỉnh, và tất cả đều có quan điểm rất mạnh về cấu trúc mã. Kiểu như “hãy tạo một scene tree chứa callback. Không, hãy viết entity, component class và object. Khoan, giờ tất cả là immediate mode, functional và stateless”
Chưa kể còn đống hỗn loạn nền tảng. Cộng thêm push notification của game di động, in-app purchase, ký bắt buộc qua Xcode nữa thì thành một mớ bòng bong hoàn toàn. Unity có thị phần là có lý do, và không phải vì nó là phần mềm xuất sắc. Cross-platform vượt quá một webview hào nhoáng vẫn là nỗi đau khổng lồ
Nếu vẫn phải liên tục viết mã riêng cho từng GPU/driver thì lớp abstraction không còn ý nghĩa. Tách driver cấp thấp và thư viện cấp cao sẽ tốt hơn
Bài viết này có nhiều lời khuyên hay. Đặc biệt câu “đừng triển khai nếu hiện giờ chưa cần” rất nổi bật
Đây là điều tôi luôn phải tranh luận với các lập trình viên junior đã có vài năm kinh nghiệm nhưng vẫn đang trưởng thành. Họ thường bị ám ảnh bởi “best practice” và những công cụ mới hào nhoáng đang thịnh hành, nhưng lại gặp khó khi bắt đầu từ vấn đề cần giải quyết và tập trung vào mức tối thiểu cần thiết để giải quyết vấn đề đó
Trong các tổ chức vừa và lớn thì thường không như vậy. Thông thường mọi người chuyển sang việc tiếp theo và hầu như không có thời gian quay lại xem. Đáng tiếc là vì thế phải làm đúng ngay từ đầu và giảm khả năng tạo ra bug hoặc tác dụng phụ
Trong quá nhiều codebase, việc vội vã thêm tính năng mới đã biến code thành một bãi mìn, đến mức chỉ một thay đổi nhỏ cũng buộc phải kiểm tra thủ công mọi tính năng và giữ toàn bộ ngữ cảnh ứng dụng trong đầu
Theo kinh nghiệm của tôi, khi yêu cầu mới xuất hiện sau này, giải pháp tổng quát đã làm trước đó thường hoàn toàn không phù hợp và cuối cùng vẫn phải làm lại. Hãy giải quyết vấn đề trước mắt, đừng giải quyết những vấn đề tương lai chưa biết
Vì vậy cũng không lạ khi mọi người muốn tự tay viết thứ gì đó ở mọi cơ hội có thể, chỗ này chỗ kia. Những việc lặt vặt kiểu thợ sửa ống nước số này làm con người kiệt sức, nên để giữ sự tỉnh táo lâu hơn một chút, đôi khi người ta muốn chen vào vài việc thú vị hơn
Có vẻ site đã sập vì lượng truy cập tăng vọt nên tôi để lại bản cache: https://web.archive.org/web/20240606103630/https://edw.is/le...
Bài viết rất hay. Tôi đã tự học Vulkan để làm một engine trực quan hóa dữ liệu khoa học: https://datoviz.org/ hiện vẫn còn khá thử nghiệm và sắp ra phiên bản mới
Trước đó tôi có kiến thức OpenGL, nhưng học Vulkan thật sự rất khó. 5 năm trước tài liệu học cũng không tốt lắm. Dù vậy tôi vẫn thử thách bản thân và thấy rất thú vị
Tôi mất vài tháng để hiểu vai trò của hàng chục lớp trừu tượng, và trong quá trình đó cũng viết một wrapper nhỏ để dùng Vulkan bớt đau khổ hơn một chút: https://datoviz.org/api/vklite/
Wrapper này chỉ hỗ trợ một số chức năng cần thiết nhất cho trực quan hóa khoa học
Đạt hiệu năng tốt hơn OpenGL bằng Vulkan không hề đơn giản. Trong driver Vulkan thiếu khoảng 20 nghìn dòng code mà driver OpenGL từng làm thay cho bạn để thiết lập rendering pipeline và render target
Phần code đó đã nằm sẵn trong driver OpenGL, và đã được những người giỏi nhất trong ngành tối ưu hơn 20 năm qua
Vì thế, việc ngây thơ lắp ráp trên Vulkan một thứ tương đương với các tính năng OpenGL cung cấp mặc định sẽ không tự nhiên tạo ra hiệu năng tốt như phép màu. Cần nhiều công sức hơn, và các vấn đề thật sự bắt đầu chất đống, như đặt đúng fence và các primitive đồng bộ
Chỉ khi bạn thực sự biết mình đang làm gì, và có thể chạy rendering với mức song song tốt cùng đồng bộ đúng, bạn mới có thể mơ tới lợi thế hiệu năng của Vulkan
Là một lập trình viên sở thích, tôi dùng OpenGL ES3 vì sự đơn giản. Với tôi nó đã đủ tốt, và tôi còn nhiều việc cấp bách hơn là viết mấy descriptor descriptor descriptor đỉnh phiền phức của Vulkan
Nhân tiện, engine của tôi ở đây: https://github.com/ensisoft/detonator
Tôi nghe nói Vulkan giờ cho phép bindless texture, nên mớ hỗn độn descriptor có lẽ đã đỡ kinh khủng hơn trước
Vulkan hấp dẫn, nhưng có chi phí ban đầu cao mà tôi không muốn trả
Sau đó có thể dần thay thế thư viện đó bằng các routine đã tối ưu theo use case của mình
Việc có thêm tài liệu về Vulkan là tốt, nhưng tài liệu này cũng gặp đúng vấn đề tôi từng thấy ở mọi tài liệu Vulkan có hiển thị thứ gì đó lên màn hình
Tất cả đều đưa thêm một lớp trừu tượng khác lên trên Vulkan trước cả khi cho thấy một trường hợp đơn giản. Lúc nào cũng bảo dùng vk-bootstrap, volk, vma hoặc thư viện khác
Tôi không biết có tài liệu nào đưa ra ví dụ quản lý bộ nhớ thủ công không. Có vẻ chỉ có hai lựa chọn: dùng vma hoặc tự đào sâu vào đặc tả. Việc yêu cầu ví dụ cơ bản nhất, không thêm thư viện nào ngoài chính Vulkan SDK, có quá đáng đến thế không?
Phần lớn game có khoảng ba loại vòng đời: vĩnh viễn/lúc khởi động, theo level, theo frame
Các vòng đời này lồng nhau, nên chỉ với một stack allocator cũng có thể đi khá xa. Khi frame hoặc level kết thúc thì chỉ cần đưa về vị trí bắt đầu
Cũng có các mẫu phức tạp hơn, nhưng chỉ cách này thôi đã khá hữu ích và có thể dùng ở cả CPU lẫn GPU
Theo nghĩa đó, việc dùng rộng rãi các thư viện bên trên là rất bình thường. Cũng giống như ngày nay ta không viết phần mềm trực tiếp dựa vào system call
Khi bạn đối chiếu hướng dẫn khởi tạo Vulkan của ai đó với mã trong kho của Khronos Group, rồi đọc đặc tả Vulkan 1.3, và nhận ra rằng để làm một việc gì đó bạn phải đọc đặc tả theo kiểu xáo trộn chứ không phải theo thứ tự, thì rõ ràng là họ đã thất bại
Họ đã thất bại. Xét theo tiêu chuẩn khác thì đó cũng là một công việc tệ. Chỉ là làm một lần xong rồi phần lớn có thể quên đi, nên các chuyên gia không phàn nàn nhiều
Tôi đã để lại một phần mã nguồn trong một bình luận khác của luồng này, có chú thích các chương và mục của đặc tả. Đó là một triển khai chung có thể dùng cùng SDL, v.v.
Tính đến thời điểm viết, cách tiếp cận tiêu chuẩn là dùng VMA và Volk đi kèm trong Vulkan SDK chính thức. Chỉ riêng điều này cũng nói lên đủ về tình trạng hiện tại
Trong vài năm tôi đã thỉnh thoảng cố học Vulkan. Trước đây tôi từng khá rành OpenGL ES 2 và 3
Một trong những điều khó là hiểu phải dùng nó thế nào trong một engine thực tế, chứ không phải trong sample. Nhiều sample chỉ cấp phát đúng vừa đủ, hoặc cấp phát hàng trăm cái để không bao giờ thiếu
Khi học DirectX, MiniEngine của Microsoft đã giúp ích, vì nó có những thứ như DescriptorAllocator để quản lý việc cấp phát descriptor mà không quá phức tạp. Tôi tự hỏi Vulkan có thứ tương tự không
Một khó khăn khác là biết cách tạo các trừu tượng tốt như material, mesh, thứ tự render. Có engine hoặc framework tốt nào đáng nghiên cứu để vượt qua mức tutorial không?
Với cấp phát descriptor set, với tôi chỉ có một mẫu là hợp lý. Nên giả định pool có vòng đời ngắn và được tạo nhiều. Nếu cấp phát từ pool hiện tại thất bại thì tạo pool mới, và không tự duy trì bộ đếm số descriptor. Tiêu chuẩn cho phép đủ loại hành vi pool khác với kiểu đếm đơn giản. Chỉ cần bỏ các pool cũ sau khi command buffer cuối cùng tham chiếu đến pool đó đã kết thúc
Pipeline barrier và image layout thật sự rất đau đầu. Tốt nhất là trừu tượng hóa bằng một lớp theo dõi lần sử dụng cuối và định dạng cuối của mọi resource, rồi thêm các barrier cần thiết. Việc này có thể trở nên phức tạp, nhưng khi xuất hiện các tình huống phức tạp hơn như có pass tùy chọn hoặc có thể thay đổi thứ tự pass thì rất đáng làm
Mesh, material và thứ tự render thì khó tóm tắt trong một bình luận HN, và phụ thuộc rất nhiều vào lựa chọn thuật toán render. Tôi cho rằng công sức khổng lồ để làm một giải pháp thật sự tổng quát là không đáng
Nếu bạn là độc giả nhẹ nhàng muốn biết cần những gì để viết “Hello, Triangle!” trong Vulkan 1.3, hãy xem cái này: https://github.com/Planimeter/game-engine-3d/blob/main/src/g...
Khởi tạo Vulkan và quản lý swapchain cơ bản rất dài dòng, nhưng sau khi làm một lần rồi về sau tạo các trừu tượng tiện lợi cho việc tạo và quản lý pipeline thì mọi thứ sẽ tốt hơn nhiều