- นักพัฒนาที่มีประสบการณ์กับ OpenGL มาก่อน เริ่มเรียนรู้ Vulkan เป็นครั้งแรก และภายในเวลาราว 3 เดือน ก็สร้างเกมเดโมขนาดเล็ก 2 เกมและเอนจิน EDBR ที่นำกลับมาใช้ซ้ำได้
- แทนที่จะออกแบบเอนจินเอนกประสงค์ตั้งแต่ต้น ผู้เขียนเลือกสร้างเกมเล็ก ๆ ก่อน แล้วค่อยดึงเฉพาะส่วนที่จำเป็นมาแปลงเป็นเอนจิน ช่วยลดการออกแบบเกินความจำเป็นและการ bike-shedding
- เอนจินมีขนาด 19k LoC โดยประกอบด้วยโค้ดกราฟิก 6.7k LoC และเลเยอร์ abstraction แบบบางสำหรับ Vulkan 2k LoC พร้อมฟีเจอร์ compute skinning, CSM, PBR shading, MSAA, post FX และ UI rendering
- ใช้
vk-bootstrap, Vulkan Memory Allocator,volk,VK_KHR_dynamic_rendering, push constants, buffer device address และ bindless descriptor เพื่อลด boilerplate ของ Vulkan และลดการใช้ descriptor set - Vulkan มอบข้อดีอย่างการตัด global state ออก, ข้อความ validation error ที่ดีกว่า, การดีบักเชดเดอร์ผ่าน RenderDoc และความสอดคล้องระหว่าง GPU กับ OS แต่ explicit synchronization ก็ยังต้องจัดการเอง
EDBR เอนจินบน Vulkan ที่สร้างขึ้นใน 3 เดือน
- EDBR (Elias Daler’s Bikeshed Engine) เริ่มต้นจากโปรเจกต์เรียนรู้ Vulkan ก่อนจะพัฒนาต่อเป็นเอนจินขนาดเล็กที่นำไปใช้ซ้ำในโปรเจกต์ถัดไปได้
- โค้ดของเอนจินและเกมเปิดเผยไว้ใน GitHub repository
- ขนาดโค้ด ณ เวลาที่เขียนมีดังนี้
- ตัวเอนจินเอง: 19k LoC
- โค้ดที่เกี่ยวข้องกับกราฟิก: 6.7k LoC
- Vulkan abstraction แบบบาง: 2k LoC
- เกมแมว 3D: 4.6k LoC
- เกมแพลตฟอร์ม 2D: 1.2k LoC
- ตัวเอนจินเอง: 19k LoC
- โค้ดที่ไม่ใช่กราฟิกบางส่วน เช่น ระบบรับอินพุตและระบบเสียง ถูกนำมาจากเอนจินตัวก่อนหน้า ขณะที่กราฟิกและหลายระบบหลักถูกเขียนขึ้นใหม่
- ผู้เขียนประเมินว่าการเขียนใหม่ทั้งหมดเหมาะสมกว่าการพยายามยัด Vulkan เข้าไปใน abstraction ของ OpenGL เดิม
ลำดับการเรียนรู้จากกราฟิกโปรแกรมมิงไปสู่ Vulkan
- หากเพิ่งเริ่มต้นกับกราฟิกโปรแกรมมิง การเริ่มจาก OpenGL จะช่วยไม่ให้ถูกความซับซ้อนของ Vulkan กลบได้ง่ายกว่า
- เป้าหมายขั้นต่ำที่แนะนำคือการแสดงโมเดลที่มีเท็กซ์เจอร์บนหน้าจอและทำแสงแบบ Blinn-Phong อย่างง่าย
- shadow mapping ขั้นพื้นฐานช่วยให้เข้าใจการเรนเดอร์ฉากจากมุมมองอื่นไปยัง render target อีกตัว และการ sample จาก depth texture
- แหล่งเรียนรู้ OpenGL ที่แนะนำมีดังนี้
- สำหรับการเรียนรู้ Vulkan แหล่งที่ช่วยได้มากที่สุดคือ vkguide และหากเพิ่งเริ่มต้นก็ควรทำตามทั้งหมด แต่สำหรับเกมขนาดเล็ก ความซับซ้อนระดับ “GPU driven rendering” อาจยังไม่จำเป็นในทันที
- Vulkan Lecture Series by TU Wien ครอบคลุมพื้นฐานของ Vulkan และโดยเฉพาะบทเรียนเรื่อง synchronization ที่มีประโยชน์มาก
- ผลลัพธ์จากการเรียนในเดือนแรกคือสามารถทำฟีเจอร์ต่อไปนี้ได้
- โหลดโมเดล glTF
- compute skinning
- frustum culling
- shadow mapping และ cascaded shadow maps
เหตุผลที่เลือก Vulkan และการเปรียบเทียบกับ WebGPU
- เป้าหมายคือเกม 3D ขนาดเล็กสำหรับเดสก์ท็อปที่เน้น Windows และ Linux และด้วยความชอบเทคโนโลยีโอเพนซอร์สกับมาตรฐานเปิด จึงเลือกพิจารณาระหว่าง OpenGL และ Vulkan
- OpenGL เพียงพอสำหรับเกมเล็ก ๆ แต่ถูกมองว่ามีอนาคตไม่แน่นอนเพราะมีโอกาสน้อยที่จะมีเวอร์ชันใหม่ และบน macOS ก็อยู่ในสถานะ deprecated
- ผู้เขียนเรียนรู้ WebGPU อยู่บ้างเช่นกัน แต่พบข้อจำกัดดังนี้
- ยังไม่เสถียรนัก และยังมีบทเรียนกับตัวอย่างไม่มาก
- ไวยากรณ์ของ WGSL ถูกใจน้อยกว่า GLSL
- บนเดสก์ท็อปมันใกล้เคียงกับการเป็น wrapper บน DirectX, Vulkan และ Metal ทำให้ RenderDoc capture แตกต่างกันตามแพลตฟอร์ม และการเรียก WebGPU ไม่ได้สอดคล้องแบบ 1:1 กับการเรียก native API
- ไม่มี bindless textures และ push constants
- อย่างไรก็ตาม WebGPU ก็มีข้อดีชัดเจนเช่นกัน
- ให้ validation error ที่ดีกว่า OpenGL/WebGL และไม่มี global state
- มีหลายส่วนคล้าย Vulkan จึงช่วยปูพื้นก่อนเรียน Vulkan ได้
- มี boilerplate น้อยกว่า Vulkan สำหรับการแสดงผลอะไรบางอย่างขึ้นจอ
- ไม่ต้องจัดการ explicit synchronization เอง
- สามารถรันเกมในเบราว์เซอร์ได้
ลำดับการเรนเดอร์เฟรม
- หนึ่งเฟรมถูกแบ่งออกเป็นหลายขั้นตอน โดยแต่ละขั้นตอนถูกทำเป็น pipeline หรือ pass
- ในขั้น skinning โมเดลที่มี skeletal animation จะถูกประมวลผลด้วย compute shader
- อินพุตคือ mesh ที่ยังไม่ผ่าน skinning และ joint matrices
- เอาต์พุตคือ vertex buffer สำหรับใช้ในขั้นตอนเรนเดอร์ถัดไป
- ทำให้ในขั้นตอนถัดไปสามารถจัดการ static mesh และ skinned mesh ได้คล้ายกัน
- ในขั้น CSM จะทำ cascaded shadow mapping โดยใช้ depth texture ขนาด 4096x4096 และ 3 slice
- ในขั้น geometry + shading จะวาดโมเดลและทำ shading โดยใช้ shadow map กับข้อมูลแสง
- โมเดล PBR ใช้วิธีการที่แทบเหมือนกับที่อธิบายไว้ใน Physically Based Rendering in Filament
- fragment shader จะคำนวณแสงทั้งหมดที่มีผลต่อ mesh นั้นภายในการ draw call เดียว
- ทุกอย่างจะถูกวาดลงบน multi-sampled texture ก่อน แล้วจึง resolve
- depth resolve ถูกจัดการแบบแมนนวลใน fragment shader
- โดยวนดูทุก fragment ของ multi-sample depth texture แล้วเขียนค่าต่ำสุดลงใน non-MS depth texture
- ขั้น post FX ตอนนี้ใช้เพียง depth fog และมีแผนจะเพิ่ม tone mapping กับ bloom ในขั้นนี้ภายหลัง
- ในขั้น UI จะวาด dialogue UI และประมวลผลทั้งหมดด้วย draw call เดียว
ไลบรารีสำหรับลด boilerplate ของ Vulkan
vk-bootstrapช่วยลด boilerplate ในขั้นเริ่มต้นของ Vulkan เช่น การเลือก physical device และการสร้าง swapchain- มันไม่ได้เป็น wrapper ครบชุดของฟังก์ชัน Vulkan แต่เน้นช่วยในช่วง initialization เป็นหลัก
- Vulkan Memory Allocator ช่วยให้ไม่ต้องจัดการการจัดสรรหน่วยความจำของ Vulkan ด้วยตัวเอง
volkช่วยทำให้การโหลด extension function ง่ายขึ้น- ตัวอย่างเช่น สามารถใช้ extension function อย่าง
vkSetDebugUtilsObjectNameEXTได้โดยไม่ต้องเก็บ pointer ด้วยตัวเอง
- ตัวอย่างเช่น สามารถใช้ extension function อย่าง
- คลาส
GfxDeviceรวบรวมฟังก์ชันและอ็อบเจ็กต์ของ Vulkan ที่ใช้บ่อยไว้ด้วยกัน- การเริ่มต้น Vulkan context
- การสร้างและจัดการ swapchain
beginFrameและendFrame- การสร้าง image และการโหลด texture
- การสร้าง buffer
- การจัดการ bindless descriptor set
GfxDevice.cppมีความยาว 714 บรรทัด ณ เวลาที่เขียน และการส่งอ็อบเจ็กต์เดียวแทนการส่งต่อVkDevice,VkQueue,VmaAllocatorฯลฯ ไปหลายจุดนั้นสะดวกกว่า
การคอมไพล์เชดเดอร์และกลยุทธ์หลีกเลี่ยง descriptor set
- เลือกใช้ภาษาเชดเดอร์เป็น GLSL เพราะมีประสบการณ์กับ OpenGL มาก่อน
- คอมไพล์เชดเดอร์เป็น SPIR-V ในขั้นตอนบิลด์ ไม่ใช่ตอนรันไทม์
- ทำให้โค้ดโหลด shader ตอนรันไทม์เรียบง่ายขึ้น
- ไม่ต้องพึ่งพา runtime shader compiler
- ตรวจพบข้อผิดพลาดของ shader ได้ตั้งแต่ขั้นตอนบิลด์
glslcสามารถกำหนดDEPFILEของ CMake ได้ จึงคอมไพล์ไฟล์ที่เกี่ยวข้องใหม่โดยอัตโนมัติเมื่อมีการเปลี่ยนแปลง shader include- ใน Vulkan ต้องจัด uniform เป็น descriptor set ทำให้การส่งข้อมูลซับซ้อนกว่า OpenGL
- ในการใช้งานจริงจึงลดการใช้ descriptor set ลงอย่างมาก
- ใช้ global descriptor set เพียงชุดเดียวสำหรับ bindless texture และ sampler
- ที่เหลือส่วนใหญ่ส่งผ่าน push constants
- ใช้ buffer device address เพื่อส่งที่อยู่ของบัฟเฟอร์ผ่าน push constants
pipeline class และ dynamic rendering
- แยกขั้นตอนเรนเดอร์ออกเป็น pipeline class เช่น
PostFXPipeline - โดยทั่วไปแต่ละ pipeline มีหน้าที่ดังนี้
init: โหลด shader, เตรียมVkPipeline,VkPipelineLayoutcleanup: จัดการเก็บกวาด pipeline และ layoutdraw: รับอินพุตที่ต้องใช้ในแต่ละเฟรมแล้วเรียก draw call
- ถือว่า
drawถูกเรียกระหว่างvkCmdBeginRenderingกับvkCmdEndRendering - pipeline ไม่ต้องสนใจว่ากำลังเรนเดอร์ลง texture ใด โดยผู้เรียกจะเป็นผู้กำหนด render target
- ใช้
VK_KHR_dynamic_renderingตลอดทั้งระบบ และไม่ใช้ Vulkan render pass หรือ subpass- แม้จะเคยได้ยินว่า render pass และ subpass มีประสิทธิภาพดีกว่าบน GPU แบบ tile-based แต่ตอนนี้ยังไม่พิจารณารองรับมือถือ
- dynamic rendering ทำให้การพัฒนาง่ายขึ้นมาก
การใช้ PVP, BDA และ bindless descriptor
- ใช้ vertex type เดียวกับทุก mesh
- เมื่อใช้ programmable vertex pulling ก็หลีกเลี่ยงการกำหนด vertex format แบบ OpenGL VAO หรือ
VkVertexInputBindingDescription,VkVertexInputAttributeDescriptionของ Vulkan ได้ - เมื่อใช้ buffer device address ก็ไม่ต้อง bind vertex buffer เข้ากับ descriptor set แต่ส่งที่อยู่ของบัฟเฟอร์ผ่าน push constants ได้เลย
- ใช้ layout แบบ
scalarกับทั้ง push constants และบัฟเฟอร์- จัดการ alignment ได้ง่ายกว่า
std430จึงใช้งานได้เกือบเหมือนโครงสร้าง C++ - ลดความจำเป็นในการมีสมาชิก padding ในโครงสร้าง C++
- จัดการ alignment ได้ง่ายกว่า
- bindless descriptor ใช้ในรูปแบบ descriptor set ขนาดใหญ่ที่มีอาร์เรย์ของ texture และ sampler
- เมื่อโหลด texture ใหม่ จะใส่เข้าไปในอาร์เรย์
texturesแล้วใช้ index นั้นเป็น bindless texture id - ส่ง texture id เข้า shader ผ่าน push constants
- เมื่อโหลด texture ใหม่ จะใส่เข้าไปในอาร์เรย์
- แยก sampler ออกจาก image แล้วสร้าง common sampler ตอนเริ่มระบบ จากนั้นใส่ไว้ในอาร์เรย์
samplers - ใน material buffer ก็ใช้ bindless texture id เช่นกัน
- ส่งเพียง material ID ผ่าน push constants แล้วให้ fragment shader ไปอ่าน material buffer
- ทำให้เข้าถึง texture ได้ด้วยเลขจำนวนเต็มหนึ่งตัวต่อ material โดยไม่ต้องมี descriptor set ขนาดใหญ่
- สำหรับเอกสารอ้างอิงเกี่ยวกับ bindless texture แนะนำ Vulkan Bindless Texture
ข้อมูลไดนามิกที่อัปโหลดทุกเฟรม
- ข้อมูลที่ต้องส่งจาก CPU ไป GPU ทุกเฟรม ใช้วิธีจองอาร์เรย์ขนาดใหญ่ไว้ล่วงหน้าแล้วเติมข้อมูลตั้งแต่ index 0 ใหม่ทุกเฟรม
- ตัวอย่างเช่น เก็บ joint matrix ทั้งหมดไว้ในอาร์เรย์
mat4ขนาดใหญ่ชุดเดียว แล้วส่ง index เริ่มต้นของแต่ละ skinned mesh ผ่าน push constants - มีอยู่สองแนวทาง
- มีบัฟเฟอร์บน GPU จำนวน N ชุด แล้วสลับใช้ตาม frame-in-flight
- มีบัฟเฟอร์บน GPU เพียงชุดเดียว และมี staging buffer ฝั่ง CPU จำนวน N ชุด
- โดยมากแล้วแนวทางแรกเป็นตัวเลือกที่แนะนำ
- แม้ใช้หน่วยความจำ GPU มากกว่า แต่ไม่ต้องทำ synchronization ด้วยตนเอง
- หากต้องประหยัดหน่วยความจำ GPU แนวทางที่สองก็อาจมีประโยชน์
- แม้จะยังไม่พบความต่างด้านประสิทธิภาพที่สังเกตได้ชัดระหว่างสองวิธี แต่ถ้ามีการอัปโหลดข้อมูลขนาดใหญ่มากทุกเฟรมก็อาจเริ่มเห็นความต่าง
cleanup และ synchronization
- รูปแบบ deletion queue ของ vkguide ไม่ได้มีประโยชน์มากนักในเอนจินของผู้เขียน
- เพราะไม่ได้สร้างหรือทำลายออบเจ็กต์ Vulkan ใหม่ทุกเฟรม
- การทำ cleanup โดยอาศัย C++ destructor ก็ไม่สะดวกเช่นกัน
- ต้องมี wrapper class, move constructor และ move assignment ทำให้ความซับซ้อนเพิ่มขึ้น
- มีความเสี่ยงที่ออบเจ็กต์ที่กำลังถูกใช้งานกลางเฟรมจะถูกลบโดยไม่ตั้งใจจากการทำลาย wrapper
- ตอนนี้จึงเรียกฟังก์ชัน
cleanupแบบชัดเจนเพื่อจัดการออบเจ็กต์ Vulkan ในจุดเดียว- แม้จะลืมเรียกได้ง่าย แต่ตอนปิดโปรแกรม Vulkan validation error และ VMA assert จะช่วยบอกว่า cleanup ส่วนใดหายไป
- synchronization ใน Vulkan เป็นเรื่องยากและต้องจัดการอย่างชัดเจน
- OpenGL และ WebGPU จะช่วยจัดการ synchronization ที่จำเป็นเมื่อมีการอ่าน texture หรือบัฟเฟอร์
- แต่ใน Vulkan ต้องใส่ barrier เองเพื่อหลีกเลี่ยง data race
- ปัจจุบันแบ่ง draw ออกเป็น pass และ pipeline แล้วแทรก barrier เองระหว่างส่วนเหล่านั้น
- ตัวอย่างเช่น หลังจาก skinning pass ของ compute shader เขียน vertex data แล้ว ต้องใส่ barrier ก่อนที่ shadow mapping pass จะมาอ่านข้อมูลนั้น
- แม้จะทำให้เป็นอัตโนมัติด้วย render graph ได้ แต่ตอนนี้ก็ยังพอใจกับการทำ synchronization แบบแมนนวล
- synchronization validation layer ของ
vkconfigช่วยตรวจหาข้อผิดพลาดด้าน synchronization ได้
sprite, skinning และการแยก game/renderer
- เมื่อใช้ bindless texture ก็วาด sprite จำนวนมากด้วย draw call เดียวได้ง่ายโดยไม่ต้องมี vertex buffer
- vertex shader ของ sprite สร้างพิกัด vertex และ UV ของ quad จาก
gl_VertexIndex - draw call ของ sprite ทั้งหมดจะถูกรวบรวมเป็น
SpriteDrawCommandในSpriteDrawBuffer- transform
- ช่วง UV
- สี
- texture ID
- shader ID
- draw call จริงมีรูปแบบเป็น
vkCmdDraw(cmd, 6, spriteDrawCommands.size(), 0, 0)- 6 vertex ต่อ 1 sprite
- จำนวน instance เท่ากับจำนวน sprite
- sprite renderer สามารถวาด sprite 10,000 ตัวได้ในเวลา 315 ไมโครวินาที
- compute skinning รับ input vertex และ joint matrices จาก mesh ที่มี skeletal animation แล้วสร้าง skinned vertex buffer ขึ้นมา
- แมว 3 ตัวที่ใช้ mesh เดียวกันก็ยังมี animation คนละชุดกันได้
- output vertex buffer ต้องมีแยกตามแต่ละ mesh instance
- แยก game logic กับ renderer ออกจากกันด้วย draw command
- game logic ใช้ entt
- renderer ไม่รู้จัก entity หรือ game object และจัดการเฉพาะ light, scene parameter และ mesh draw command
MeshDrawCommandมีmeshId, transform matrix, bounding sphere, พอยน์เตอร์ไปยัง skinned mesh, index เริ่มต้นของ joint matrix และสถานะว่าจะ cast shadow หรือไม่
การโหลดซีน, UI, Dear ImGui
- ไม่ได้สร้าง level editor ขึ้นมาเอง แต่ใช้ Blender และ export เป็น glTF
- หากเขียน level editor เองอาจใช้เวลาหลายเดือนหรือหลายปี จึงช่วยประหยัดเวลาได้
- ใช้ชื่อนोडในการสร้าง prefab และกำหนด physics shape
- ตัวอย่าง: ใน
Interact.Sphere.Diaryคำว่าInteractก่อนจุดแรกคือชื่อ prefab Sphereใช้ในระบบ physics เมื่อต้องสร้าง sphere physics body- สามารถใช้
Capsule,Boxได้เช่นกัน และหากไม่มีจะสร้าง physics shape จาก mesh vertex
- ตัวอย่าง: ใน
- โมเดลที่ซับซ้อนไม่ได้ใส่ลงใน level glTF โดยตรง แต่จะวางเป็นอ็อบเจ็กต์
Empty->Arrowsแล้วตั้งชื่ออย่างCat.NearStore- สร้าง
Catprefab และติดแท็กNearStore
- สร้าง
- prefab เขียนเป็น JSON และสามารถรวมข้อมูล glTF ภายนอก รวมถึงข้อมูล movement และ physics ได้
- ระบบ UI ได้แรงบันดาลใจจาก Roblox UI API
- origin
- relative size
- relative position
offsetPosition,offsetSize- fixed size
- ขนาดตามเนื้อหาของ label/image
- คำนวณขนาดของ UI element แบบเรียกซ้ำก่อน จากนั้นค่อยคำนวณตำแหน่ง และวาดจาก parent ไปตามลำดับของ children
- ใช้ Dear ImGui สำหรับเครื่องมือพัฒนาและดีบัก
- มีปัญหาที่ Dear ImGui แสดงผลผิดพลาดบน sRGB framebuffer จึงเขียน Dear ImGui backend ขึ้นเอง
- เขียนเฉพาะส่วน rendering ส่วน logic/การโต้ตอบกับ OS เช่นการจัดการ input event และ clipboard ให้ Dear ImGui SDL backend เดิมจัดการ
- ข้อดีของ backend ที่เขียนเองมีดังนี้
- รองรับ bindless texture id จึงวาด image ได้ด้วย
ImGui::Image(bindlessTextureId, ...) - สามารถวาดทั้ง linear image และ non-linear image ได้อย่างถูกต้องผ่านการส่ง format
- สามารถ initialize และจัดการได้ในลักษณะเดียวกับโค้ด Vulkan อื่น ๆ ของเอนจิน
- รองรับ bindless texture id จึงวาด image ได้ด้วย
ไลบรารีที่ใช้และผลจากการเปลี่ยนมาใช้ Vulkan
- ฟิสิกส์ใช้ Jolt Physics
- ใช้เป็นหลักสำหรับ collision resolution และ basic character movement
JPH::CharacterVirtualจัดการ character movement พื้นฐานได้ดี
- ECS ใช้ entt
- ใช้ไลบรารีภายนอกแทน ECS ที่ทำเองเพื่อลดโค้ดที่ต้องดูแลรักษา
- ออดิโอใช้ openal-soft, libogg, libvorbis
- profiling ใช้ Tracy
- ช่วยตรวจสอบได้ว่าโค้ดส่วนไหนใช้เวลาน้อยมากจริง ๆ และหลีกเลี่ยงการ bike-shedding ที่ไม่จำเป็น
- สิ่งที่ได้จากการเปลี่ยนมาใช้ Vulkan มีดังนี้
- ไม่มี global state แบบ OpenGL ทำให้ทำ abstraction ได้ง่ายขึ้น
- ไม่ค่อยจำเป็นต้องมี
shader.bind()แบบ OpenGL, state tracker หรือ magic RAII - validation error มีรายละเอียดมากกว่า OpenGL
- ดีบัก vertex shader และ fragment shader ได้โดยตรงใน RenderDoc
- ความแตกต่างของการทำงานระหว่าง GPU กับ OS เด่นชัดน้อยกว่า OpenGL
- ในอนาคตสามารถสำรวจ shading language อื่นอย่าง Slang, Shady ได้
- ควบคุมแต่ละแง่มุมของ graphics pipeline ได้มากขึ้น
สิ่งที่จะทำต่อไป
- งานที่วางแผนไว้ต่อจากนี้มีดังนี้
- รองรับ sign-distance field font
- โหลด image จำนวนมากและสร้าง mipmap แบบขนาน
- bloom
- volumetric fog
- animation blending
- render graph
- ambient occlusion
- ทำเกมให้เสร็จ
- การเรียนรู้ Vulkan นั้นยาก แต่ก็ไม่ได้ยากอย่างที่คาดไว้ และเป็นโอกาสให้เข้าใจ graphics programming และ modern API ได้ลึกซึ้งยิ่งขึ้น
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
มินิมัลลิสม์ มีประสิทธิภาพมาก
ผมไปในทางตรงกันข้าม และกำลังเจ็บหนักเพราะมัน ผมกำลังสร้างไคลเอนต์เมตาเวิร์สด้วย Rust และตอนนี้อีกหน้าจอก็กำลังแสดงฉากที่อวาตาร์นั่งรถรางผ่านเมืองสตีมพังก์ขนาดยักษ์อยู่ ก่อนจะออกพรีรีลีสใหม่ ผมปล่อยให้มันรันทีละ 12 ชั่วโมง
ผมวาง WGPU และ Rend3 ไว้บน Vulkan โดย Rend3 ให้ API ที่สะอาดตา: สร้าง mesh, texture 2D และ object แล้ว object ก็อ้างอิงถึง mesh กับ texture และปรากฏบนหน้าจอ การนับ reference ของ Rust จัดการความเชื่อมโยงให้พอดี จึงใช้งานได้ค่อนข้างตรงไปตรงมา
แต่พอมีหลายชั้นขึ้นก็เกิดปัญหา WGPU พยายามรองรับทั้งเว็บเบราว์เซอร์, Vulkan, Metal, DX11 (เพิ่งถูกเอาออก), DX12, Android, OpenGL จึงต้องมีทีมพัฒนาขนาดใหญ่และเปลี่ยนแปลงได้ยาก ตัว WGPU API เองโดยรวมก็คล้าย Vulkan เลยต้องคอยใส่ใจเรื่องการจัดสรรหน่วยความจำ GPU และการซิงโครไนซ์เอง
WGPU มีปัญหาเรื่อง ตัวหารร่วมต่ำสุด บางแพลตฟอร์มรองรับฟีเจอร์บางอย่างไม่ได้ และ WGPU ไม่สามารถจัดการการอัปเดตหน่วยความจำ GPU พร้อมกันจากหลายเธรดแบบไม่รบกวนกัน ซึ่ง Vulkan รองรับได้ เกมหรือไคลเอนต์ที่จัดการโลกขนาดใหญ่จำเป็นต้องมีความสามารถนี้เพื่อใส่คอนเทนต์เข้า GPU โดยไม่ฆ่าเฟรมเรต ข้อจำกัดด้าน concurrency ของแต่ละแพลตฟอร์มต่างกัน จนอาจทำให้ประสิทธิภาพตกหนักจากการชนกันของ lock ได้
Rend3 พยายามทำหน้าที่เป็นโค้ดเชื่อมที่เหมาะสมเพื่อจัดการการซิงโครไนซ์และการจัดสรร แต่โดยเฉพาะการแก้ปัญหาการซิงโครไนซ์แบบทั่วไปนั้นยาก Frustum culling ให้ผลด้านประสิทธิภาพสูงมาก แต่ occlusion culling กลับไม่คุ้มเพราะต้นทุนการคำนวณ การจัดการความโปร่งแสงก็ปวดหัวเพราะต้องเรียงตาม depth ด้วย เนื่องจากโลกที่ผมทำมีหน้าต่างเยอะ จึงจำเป็นต้องมีวัตถุกึ่งโปร่งใสที่มองออกไปข้างนอกและมองเข้าไปข้างในได้
คนในฝั่งสแตก 3D ของ Rust ดูเหมือนจะรำคาญที่ผมกดดันให้แก้สแตกมาตลอด 3 ปี ทุกคนเป็นอาสาสมัคร ส่วน Vulkan อยู่ได้เพราะมีเงินและฐานผู้ใช้ ช่วงหลังผู้สร้าง Rend3 ยอมแพ้ไปแล้ว ตอนนี้ผมเลยต้องเข้าไปข้างในแล้วแก้เอง คนที่สร้างของซับซ้อนด้วย WGPU มีน้อยมาก ส่วนใหญ่เป็นเกม 2D หรือฉาก 3D แบบ static ง่าย ๆ ที่แม้แต่ Flash ก็น่าจะทำได้ โปรเจกต์เชิงพาณิชย์ก็ยังใช้ Unity หรือ UE5 กันต่อไป
ต่อให้ไปใช้ Vulkan โดยตรง ก็ยังต้องเขียนการซิงโครไนซ์, การจัดสรร, frustum culling และการจัดการความโปร่งแสงเองอยู่ดี จึงเป็นการเปลี่ยนแปลงครั้งใหญ่
เพิ่มเติม Vulkano ซึ่งเป็น wrapper บน Vulkan และ Metal ก็มีปัญหาตัวหารร่วมต่ำสุดเหมือนกัน Vulkan และ Metal ต่างก็รองรับการอัปเดต asset ของ GPU พร้อมกัน แต่ Vulkano ไม่อนุญาต แน่นอนว่า Apple ก็จัดการต่างออกไปอีก
https://gpuweb.github.io/gpuweb/explainer/#multithreading
https://github.com/gpuweb/gpuweb/issues/354
OpenGL ก็ไม่เคยรองรับ thread ดังนั้นการใช้ OpenGL จึงเป็นแบบนั้นไม่ได้
เมื่อประมาณปีกว่า ๆ ก่อนเคยพยายามเรียน Vulkan แต่ไม่อยากแตะมันอีกแล้ว รู้สึกอึดอัดมากที่เลิกใช้ OpenGL แล้วแทนที่ด้วยสิ่งที่ทำให้งานง่าย ๆ กลายเป็นเรื่องยากเกินเหตุ เช่น แค่คิวบ์หมุนได้หนึ่งลูกก็ต้องใช้โค้ดหลายร้อยบรรทัด
OpenGL ก็ไม่ได้ง่าย แต่คนทั่วไปยังเรียนพื้นฐานได้ในเวลาค่อนข้างสั้น ซื้อหนังสือแนะนำการเขียนโปรแกรมกราฟิกจากร้านหนังสือใหญ่ ๆ แล้วใช้เวลาช่วงบ่ายสักวันสองวันก็สามารถแสดงผลพื้นฐานขึ้นมาได้ Vulkan อาจดีกว่าในบางแง่ แต่การคาดหวังว่าจะเรียนได้เร็วไม่ค่อยสมจริง
ลองจินตนาการว่าพอชิป Intel/ARM/AMD รุ่นใหม่ออกมา แล้วมีคนบอกว่าใช้ C หรือ C++ ไม่ได้แล้ว “เราจะเลิกสนับสนุนภาษาระดับสูง ต่อไปให้เขียนแอสเซมบลีเท่านั้น เพราะมีสิทธิ์ควบคุมมากขึ้นและเร็วกว่า” แน่นอนว่าคงถูกมองว่าไร้สาระ
มักมีคนพูดว่า Vulkan ใกล้เคียงกับ “GPU API” มากกว่า API กราฟิกระดับสูง ถ้ามองแบบนั้นความซับซ้อนก็ไม่น่าประหลาดใจ และตัวโดเมนเองก็ยากอยู่แล้ว
อุปมาเรื่องตัดภาษาระดับสูงออกจาก CPU รุ่นใหม่แล้วให้เขียนแต่แอสเซมบลี น่าจะใกล้เคียงกับความต่างระหว่าง C/C++ แบบเธรดเดียวกับ C/C++ แบบหลายเธรดมากกว่า ความซับซ้อนเพิ่มขึ้นมาก ถ้าไม่รู้ก็พังหรือประสิทธิภาพแย่ลงได้ แต่ก็เป็นเส้นทางที่ใช้งานได้จริงในการก้าวไปข้างหน้า
โดยทั่วไป OpenGL สามารถนำไปใช้งานบน Vulkan ได้ น่าเสียดายที่มาตรฐาน OpenGL ไม่ได้พัฒนาอย่างแข็งขันแล้ว แต่ไม่มีอะไรอยู่ตลอดไป
ปัญหาคือ OpenGL ไม่เข้ากับฮาร์ดแวร์ปัจจุบันแล้ว ถ้าใช้อย่างตรงไปตรงมาจะมีประสิทธิภาพแย่มาก แม้ใน OpenGL ถ้าไปถึงเทคนิคที่ลดภาระของไดรเวอร์ Vulkan ก็ไม่ได้ยากกว่าขนาดนั้น
ชิป Intel/ARM/AMD ปัจจุบันก็ไม่ได้รองรับ C หรือ C++ โดยตรงอยู่แล้ว เราใช้แอสเซมบลี หรือใช้เครื่องมือของบุคคลที่สามที่แปลงจาก C/C++ ให้อยู่แล้ว เป้าหมายของ Vulkan ก็เช่นกัน คือให้ส่วนติดต่อมาตรฐานระดับต่ำกับ GPU แล้วให้วาง abstraction ที่ใช้งานสะดวกกว่าทับไว้ด้านบน
https://github.com/google/angle
โทรศัพท์หลายรุ่นตอนนี้มี ANGLE เป็นการรองรับ OpenGL เพียงอย่างเดียวที่อยู่บนไดรเวอร์ Vulkan
ถ้าต้องการ API สมัยใหม่ที่ค่อนข้างง่ายและพกพาได้ ก็ใช้ WebGPU ผ่าน wgpu ของ Rust หรือ dawn ของ C++ ได้
ผมคิดว่า Vulkan ยอดเยี่ยม แต่เป้าหมายของมันคือการใช้ความสามารถขั้นสูงของ GPU ให้ได้มากที่สุด เมื่อใช้ฟีเจอร์ GPU ขั้นสูง ก็ทำประสิทธิภาพได้ดีกว่า OpenGL ได้เช่นกัน
ถ้าไม่ได้ตั้งเป้าใช้เทคนิคเรนเดอร์ขั้นสูง โดยทั่วไปผมรู้สึกว่า OpenGL คือเส้นทางที่แนะนำ
ทุกวันนี้ยังมีเกม 2D, โลว์โพลี, กราฟิกสไตล์ PS1 อยู่มาก และเกมเหล่านี้ไม่จำเป็นต้องใช้ Vulkan
Vulkan เป็นตัวอย่างว่าอุตสาหกรรมเกม AAA เอนเอียงไปทางคุณภาพการเรนเดอร์และรูปลักษณ์ สตูดิโอ AAA ใช้เอนจินและคอนเทนต์ระดับสูงมากเพื่อทำให้งบประมาณดูสมเหตุสมผล แต่ตลาดเกม 2D/โลว์โพลีกำลังเติบโต เพราะเริ่มตระหนักว่าผู้เล่นเหนื่อยล้าและต้องการ เกมเพลย์ มากกว่ากราฟิก
ถ้าเป็นนักพัฒนาเกม ก็คงอยากโฟกัสที่เกมเพลย์และฟีเจอร์มากกว่าคุณภาพการเรนเดอร์
ไม่มีสถานะโกลบอล เลือก GPU ที่จะใช้ได้ตอนรันไทม์ และการจัดการข้อผิดพลาดของ OpenGL ก็แย่มาก มี validation layers อาร์ตเวิร์กทางการก็เท่ และเอกสารก็ดีเยี่ยม การอัปโหลดข้อมูลไปยัง GPU แบบอะซิงโครนัสจากเธรดที่สองก็ทำได้สมเหตุสมผลกว่า และยังมีฟีเจอร์ GPU ขั้นสูงอย่าง mesh shader หรือ RTX ด้วย
ผู้บริโภคจะปฏิเสธเกมอินดี้ที่มีกราฟิกระดับ AAA ไหม? คงไม่ใช่ เหตุผลที่เกมแบบนั้นมีน้อยคือมันทำให้คุ้มทางการเงินได้ยาก และมีตลาดที่ใหญ่พอซึ่งยอมรับกราฟิกที่มีสไตล์จัดกว่าและความเที่ยงตรงต่ำกว่า
ในขณะเดียวกันก็เข้าถึงฟีเจอร์สมัยใหม่อย่าง compute shader ที่ใช้ใน WebGL ไม่ได้ และไม่มีมรดกเก่าที่สะสมมาหลายวิธีสำหรับทำสิ่งเดียวกันแบบ OpenGL ข้อดีหลักคือมันใหม่ แต่เพราะอย่างนั้นจึงมีบทเรียนสอนน้อยกว่ามาก และนี่เป็นข้อเสียที่ค่อนข้างหนัก
เฟรมเวิร์กและเอนจินมีมากมายไม่รู้จบ แต่ส่วนใหญ่ยังไม่เสร็จ และทุกตัวมีความเห็นแรงมากเกี่ยวกับโครงสร้างโค้ด เช่น “สร้าง scene tree ที่ใส่ callback ไว้สิ ไม่สิ เขียน entity กับ component class และ object สิ เดี๋ยวก่อน ตอนนี้ทุกอย่างเป็น immediate mode เป็น functional และไร้สถานะแล้ว” ประมาณนั้น
แล้วยังต้องเจอกับความยุ่งเหยิงของแพลตฟอร์มอีก พอรวม push notification ของเกมมือถือ, in-app purchase, ไปจนถึงการเซ็นที่บังคับผ่าน Xcode ก็เละเทะไปหมด Unity มีส่วนแบ่งตลาดด้วยเหตุผลของมัน และไม่ใช่เพราะมันเป็นซอฟต์แวร์ที่ยอดเยี่ยม แต่เพราะการทำครอสแพลตฟอร์มที่มากกว่าเว็บวิวสวย ๆ ยังคงเจ็บปวดมหาศาล
ถ้ายังต้องเขียนโค้ดเฉพาะ GPU/ไดรเวอร์อยู่เรื่อย ๆ ชั้น abstraction ก็ไม่มีความหมาย แยกไดรเวอร์ระดับต่ำออกจากไลบรารีระดับสูงน่าจะดีกว่า
บทความนี้มีคำแนะนำดี ๆ อยู่มาก โดยเฉพาะ “ถ้ายังไม่จำเป็นตอนนี้ ก็อย่าเพิ่ง implement” ที่สะดุดตา
นี่เป็นเรื่องที่ผมต้องถกเถียงอยู่เสมอกับโปรแกรมเมอร์จูเนียร์ที่มีประสบการณ์มาหลายปีแล้วแต่ยังอยู่ระหว่างเติบโต พวกเขามักยึดติดกับ “best practices” และเครื่องมือใหม่ ๆ หรูหราตามกระแส แต่กลับลำบากในการเริ่มจากปัญหาที่ต้องแก้ แล้วโฟกัสเฉพาะสิ่งขั้นต่ำที่จำเป็นต่อการแก้ปัญหานั้น
ในองค์กรขนาดกลางถึงใหญ่ ปกติไม่เป็นแบบนั้น ส่วนใหญ่จะย้ายไปทำงานถัดไป และแทบไม่มีเวลากลับมาดูอีก น่าเสียดาย แต่นั่นจึงเป็นเหตุผลว่าทำไมต้องทำให้ถูกต้องตั้งแต่แรก และลดช่องทางที่จะสร้างบั๊กหรือผลข้างเคียง
ใน codebase จำนวนมากเกินไป การรีบเพิ่มฟีเจอร์ใหม่ทำให้โค้ดกลายเป็นทุ่งกับระเบิด แค่เปลี่ยนเล็กน้อยครั้งเดียวก็ต้องตรวจทุกฟีเจอร์ด้วยมือ และต้องใส่บริบทของแอปพลิเคชันทั้งหมดไว้ในหัว
จากประสบการณ์ของผม พอมี requirement ใหม่เข้ามาภายหลัง โซลูชันแบบ generic ที่เคยทำไว้ก็มักไม่เหมาะโดยสิ้นเชิง จนต้องทำใหม่อยู่ดี ควรแก้ปัญหาตรงหน้า ไม่ใช่แก้ปัญหาในอนาคตที่ยังไม่รู้จัก
ดังนั้นจึงไม่น่าแปลกที่ผู้คนจะพยายามเขียนอะไรเองในทุกโอกาสที่ทำได้ งานจิปาถะเหมือนช่างประปาดิจิทัลแบบนี้ทำให้คนหมดแรง จึงอยากแทรกงานที่สนุกกว่าเข้ามาบ้างเป็นครั้งคราว เพื่อรักษาสติให้อยู่ได้นานขึ้นอีกหน่อย
ดูเหมือนเว็บจะล่มเพราะคนเข้าเยอะ เลยทิ้งแคชไว้ให้: https://web.archive.org/web/20240606103630/https://edw.is/le...
เป็นบทความที่ยอดเยี่ยม ผมเรียน Vulkan เองเพื่อทำเอนจิน visualization สำหรับข้อมูลวิทยาศาสตร์: https://datoviz.org/ ตอนนี้ยังค่อนข้าง experimental และกำลังจะออกเวอร์ชันใหม่เร็ว ๆ นี้
ก่อนหน้านี้ผมมีความรู้ OpenGL อยู่แล้ว แต่การเรียน Vulkan ยากจริง ๆ เมื่อ 5 ปีก่อน สื่อการเรียนก็ยังไม่ค่อยดีนัก ถึงอย่างนั้นก็ลองท้าทายดู และสนุกมาก
ใช้เวลาหลายเดือนกว่าจะเข้าใจบทบาทของ abstraction หลายสิบแบบ และระหว่างนั้นก็เขียน wrapper เล็ก ๆ เพื่อให้ใช้ Vulkan ได้เจ็บปวดน้อยลงเล็กน้อย: https://datoviz.org/api/vklite/
wrapper นี้รองรับเฉพาะฟีเจอร์บางส่วนที่จำเป็นที่สุดสำหรับ visualization ทางวิทยาศาสตร์
การทำให้ Vulkan มีประสิทธิภาพดีกว่า OpenGL ไม่ใช่เรื่องง่าย ในไดรเวอร์ Vulkan ไม่มีโค้ดราว 20,000 บรรทัดที่ไดรเวอร์ OpenGL เคยทำแทนให้ในการตั้งค่า rendering pipeline และ render target
โค้ดนั้นมีอยู่แล้วในไดรเวอร์ OpenGL และถูกปรับแต่งโดยคนเก่งที่สุดในอุตสาหกรรมมานานกว่า 20 ปี
ดังนั้นการประกอบสิ่งที่เทียบเท่ากับฟีเจอร์ที่ OpenGL ให้มาโดยปริยายไว้บน Vulkan แบบตรง ๆ ไม่ได้ทำให้ได้ประสิทธิภาพดีขึ้นอย่างวิเศษ ต้องลงแรงมากกว่านั้น และปัญหาจริง ๆ ก็เริ่มกองตามมา เช่น การใส่ fence และ synchronization primitive ให้ถูกต้อง
ก็ต่อเมื่อคุณรู้จริง ๆ ว่ากำลังทำอะไร และสามารถรันการเรนเดอร์ด้วย parallelism ที่ดีและ synchronization ที่ถูกต้องได้ เมื่อนั้นถึงจะเริ่มฝันถึงข้อได้เปรียบด้านประสิทธิภาพของ Vulkan ได้
ในฐานะนักพัฒนางานอดิเรก ผมใช้ OpenGL ES3 เพราะความเรียบง่าย สำหรับผมมันดีพออยู่แล้ว และมีเรื่องเร่งด่วนกว่าการเขียน Vulkan vertex descriptor descriptor descriptor ที่น่ารำคาญอีกมาก
อ้างอิง: เอนจินของผมอยู่ที่นี่: https://github.com/ensisoft/detonator
ได้ยินว่า Vulkan ตอนนี้อนุญาต bindless texture แล้ว ดังนั้นความยุ่งเหยิงของ descriptor น่าจะไม่น่ากลัวเท่าเมื่อก่อน
Vulkan น่าสนใจ แต่มีต้นทุนเริ่มต้นสูงที่ผมไม่อยากจ่าย
จากนั้นก็ค่อย ๆ แทนที่ส่วนต่าง ๆ ของไลบรารีนั้นด้วย routine ที่ optimize ให้เหมาะกับ use case ของตัวเอง
การที่มีเนื้อหาเกี่ยวกับ Vulkan มากขึ้นเป็นเรื่องดี แต่เรื่องนี้ก็เจอปัญหาเดียวกับที่เห็นในเนื้อหา Vulkan ทุกชิ้นที่ทำอะไรสักอย่างให้แสดงบนหน้าจอ
ทุกคนเอาเลเยอร์ abstraction อีกชั้นมาครอบ Vulkan ก่อนที่จะได้แสดงกรณีง่าย ๆ ด้วยซ้ำ มักจะบอกให้ใช้ vk-bootstrap, volk, vma หรือไลบรารีอื่นเสมอ
ไม่รู้ว่ามีแหล่งไหนสักแห่งไหมที่แสดงตัวอย่างการทำ การจัดการหน่วยความจำ แบบ manual ให้ดู ดูเหมือนทางเลือกมีแค่ใช้ vma หรือไปขุดอ่านสเปกเอง การขอตัวอย่างพื้นฐานที่สุดที่ไม่เพิ่มไลบรารีใด ๆ นอกจาก Vulkan SDK เอง มันเกินไปขนาดนั้นเลยหรือ?
เกมส่วนใหญ่จะมีอายุการใช้งานโดยคร่าว ๆ สามแบบ: ถาวร/ตอนเริ่มต้น, ตามเลเวล, ตามเฟรม
อายุการใช้งานเหล่านี้ซ้อนกันอยู่ จึงใช้ stack allocator ตัวเดียวไปได้ค่อนข้างไกล เมื่อเฟรมหรือเลเวลจบ ก็แค่ย้อนกลับไปตำแหน่งเริ่มต้น
ยังมีแพตเทิร์นที่ซับซ้อนกว่านี้ แต่แค่วิธีนี้ก็มีประโยชน์มากแล้ว และใช้ได้ทั้งฝั่ง CPU และ GPU
ในแง่นั้น การใช้ไลบรารีครอบด้านบนอย่างกว้างขวางจึงเป็นเรื่องปกติมาก เหมือนกับที่ซอฟต์แวร์สมัยนี้ไม่ได้เขียนชนกับ system call โดยตรง
พอเอาคำแนะนำการ initialize Vulkan ของใครสักคนไปเทียบกับโค้ดใน repository ของ Khronos Group แล้วอ่านสเปก Vulkan 1.3 ไปด้วย ก่อนจะตระหนักว่าถ้าจะทำอะไรสักอย่างต้องอ่านสเปกแบบสลับไปมา ไม่ใช่อ่านตามลำดับ ตอนนั้นก็ชัดแล้วว่ามันล้มเหลว
พวกเขาล้มเหลวแล้ว มองด้วยเกณฑ์อื่นก็ยังเป็นงานที่แย่ เพียงแต่พอทำครั้งหนึ่งแล้วส่วนใหญ่ก็ลืมมันไปได้ ผู้เชี่ยวชาญเลยไม่ค่อยบ่นเสียงดัง
ผมทิ้งโค้ดบางส่วนไว้ในคอมเมนต์อื่นของเธรดนี้ โดยใส่คอมเมนต์อ้างอิงบทและหัวข้อของสเปกไว้ เป็น implementation ทั่วไปที่ใช้ร่วมกับ SDL และอื่น ๆ ได้
แนวทางมาตรฐาน ณ เวลาที่เขียนคือใช้ VMA และ Volk ที่รวมมากับ Vulkan SDK ทางการ แค่นี้ก็บอกสถานะปัจจุบันได้เพียงพอแล้ว
พยายามเรียน Vulkan แบบเป็นครั้งคราวมาหลายปีแล้ว สมัยก่อนรู้จัก OpenGL ES 2 และ 3 ค่อนข้างดี
หนึ่งในสิ่งที่ยากคือการเข้าใจว่าควรใช้มันอย่างไรใน engine จริง ไม่ใช่แค่ sample หลาย sample จะ allocate แค่เท่าที่จำเป็นเป๊ะ ๆ หรือไม่ก็ allocate ไว้เป็นร้อย ๆ เพื่อให้ไม่มีวันไม่พอ
ตอนเรียน DirectX นั้น MiniEngine ของ Microsoft ช่วยได้ เพราะมันไม่ซับซ้อนเกินไป และมีอย่าง DescriptorAllocator สำหรับจัดการการ allocate descriptor เลยสงสัยว่า Vulkan มีอะไรคล้าย ๆ กันไหม
อีกเรื่องที่ยากคือการรู้ว่าจะสร้าง abstraction ที่ดีสำหรับ material, mesh, ลำดับการ render และสิ่งทำนองนี้อย่างไร มี engine หรือ framework ดี ๆ ที่ควรศึกษาเพื่อก้าวข้ามระดับ tutorial ไหม?
สำหรับการ allocate descriptor set มีเพียงแพตเทิร์นเดียวที่สมเหตุสมผลสำหรับผม คือควรสมมติว่า pool มีอายุสั้นและมีจำนวนมาก ถ้า allocate จาก pool ปัจจุบันล้มเหลว ก็สร้าง pool ใหม่ และไม่ต้องคอยดูแล counter จำนวน descriptor เอง มาตรฐานอนุญาตพฤติกรรมของ pool สารพัดแบบที่ต่างจากการนับแบบง่าย ๆ แค่ทิ้ง pool เก่าหลังจาก command buffer สุดท้ายที่อ้างถึง pool นั้นจบแล้วก็พอ
pipeline barrier และ image layout เป็นเรื่องปวดหัวจริง ๆ ควรทำ abstraction ด้วยเลเยอร์ที่ติดตามการใช้งานครั้งล่าสุดและรูปแบบล่าสุดของ resource ทุกตัว แล้วเพิ่ม barrier ที่จำเป็น อาจซับซ้อนได้ แต่ถ้ามีสถานการณ์ที่ซับซ้อนขึ้น เช่นมี pass แบบ optional หรือเปลี่ยนลำดับ pass ได้ ก็จะคุ้มค่า
mesh, material และลำดับการ render สรุปให้ครบในคอมเมนต์ HN เดียวได้ยาก และขึ้นกับการเลือก rendering algorithm อย่างมาก ผมมองว่าความพยายามมหาศาลในการทำโซลูชันที่ generic มาก ๆ ให้ดีจริงนั้นไม่คุ้ม
ถ้าเป็นผู้อ่านทั่วไปที่อยากรู้ว่าใน Vulkan 1.3 ต้องมีอะไรบ้างเพื่อเขียน “Hello, Triangle!” ดูอันนี้ได้: https://github.com/Planimeter/game-engine-3d/blob/main/src/g...
การ initialize Vulkan และการจัดการ swapchain พื้นฐานนั้นยืดยาวมาก แต่พอทำสักครั้งแล้วภายหลังสร้าง abstraction ที่สะดวกสำหรับการสร้างและจัดการ pipeline ก็จะดีขึ้นมาก