- ตลอด 10 ปีที่ผ่านมา กราฟิกส์ API ระดับล่างอย่าง DirectX 12, Vulkan, Metal ช่วยรีดประสิทธิภาพ GPU ได้มากขึ้น แต่ก็ทำให้ความซับซ้อนและต้นทุนการดูแลรักษาเพิ่มขึ้นอย่างรวดเร็ว
- GPU ยุคใหม่รองรับ ลำดับชั้นแคชแบบสมบูรณ์, พอยน์เตอร์ 64 บิต, ทรัพยากรแบบ bindless ทำให้ state object และโมเดลการ bind ที่ซับซ้อนแบบเดิมไม่จำเป็นอีกต่อไป
- ดีไซน์ที่เสนอใช้ การเข้าถึงหน่วยความจำแบบพอยน์เตอร์ C/C++ และ root pointer 64 บิตเพียงตัวเดียว เพื่อลดความซับซ้อนของเรนเดอริงไปป์ไลน์อย่างมาก
- เสนอให้ตัด การระเบิดของ PSO, resource barrier, binding API ที่ซับซ้อน ออก และเชื่อม GPU memory กับ shader language เข้าหากันโดยตรง
- แนวทางนี้คือ API รุ่นถัดไปที่เหมาะกับสถาปัตยกรรม GPU สมัยใหม่ และชี้ทิศทางที่ DirectX 13 หรือ Vulkan 2.0 ควรมุ่งไป
การเปลี่ยนแปลงของกราฟิกส์ API ระดับล่าง
- ในปี 2013 สถาปัตยกรรม AMD GCN ของ Xbox One และ PS4 กลายเป็นมาตรฐานของการพัฒนาเกม AAA และทำให้เกิด API ระดับล่างอย่าง Mantle, DirectX 12, Vulkan และ Metal
- กล่าวคือ API เหล่านี้ถูกออกแบบโดยอิงกับสถาปัตยกรรม GPU ในช่วงรอบปี 2013
- DirectX 11/OpenGL แบบเดิมมีข้อจำกัดจากการเรนเดอร์แบบเธรดเดียวและ driver overhead ที่สูง
- API เหล่านี้ลดต้นทุนของ draw call ด้วย pipeline object (PSO) ที่คอมไพล์ล่วงหน้า แต่เพราะไม่สอดคล้องกับโครงสร้างเอนจิน จึงเพิ่มความซับซ้อนขึ้น
- ผลลัพธ์คือภายในเอนจินต้องมี “เลเยอร์ไดรเวอร์ระดับล่าง” เพิ่มขึ้นมาอีกชั้น และบทบาทของโปรแกรมเมอร์กราฟิกส์ก็ถูกแยกย่อยมากขึ้น
ภูมิหลังทางประวัติศาสตร์: ทำไมจึงซับซ้อนถึงเพียงนี้
- GPU ยุคแรกมีโครงสร้างที่ยึดกับหน่วยความจำแบบแยกและ fixed-function pipeline
- OpenGL และ DirectX เลือกใช้การออกแบบแบบ state-based และ object-based เพื่อ ทำ abstraction ให้กับความหลากหลายของฮาร์ดแวร์
- แม้กระทั่งใน DirectX 11 texture และ buffer ก็ยังถูกจัดการผ่าน descriptor แบบทึบแสง
- และแนวคิดการออกแบบนี้ก็ยัง สืบต่อมาแบบอาศัยแรงเฉื่อย จนถึง API รุ่นหลัง
ความไม่สอดคล้องกันระหว่าง GPU ยุคใหม่กับ API
- ปัจจุบัน GPU รองรับ ลำดับชั้นแคชที่สอดคล้องกัน, PCIe ReBAR, พอยน์เตอร์ 64 บิต, bindless texture
- จึงสามารถมี โครงสร้างที่ CPU เขียนลง GPU memory โดยตรง และ GPU อ่านได้ทันที
- ในสภาพแวดล้อมเช่นนี้ โครงสร้างอย่าง PSO, descriptor set, binding table จึงไม่จำเป็น
- ปัญหาการพอกพูนของ PSO cache ทำให้ต้องใช้แคชระดับหลายร้อย GB และกลายเป็นสาเหตุของ การโหลดล่าช้าและอาการกระตุก
- API ใหม่สามารถตัดโครงสร้างล้าสมัยเหล่านี้ออก และเปลี่ยนไปใช้ การเข้าถึงแบบพอยน์เตอร์ที่เรียบง่าย ได้
การทำให้การจัดการ GPU memory ง่ายขึ้น
- Vulkan/DirectX 12 แบบเดิมต้อง สร้าง resource แล้วค่อยตรวจสอบความเข้ากันได้ของ heap ทำให้ไม่มีประสิทธิภาพ
- แนวทางที่เสนอใช้ API แบบง่ายในรูป gpuMalloc/gpuFree เพื่อจอง GPU memory โดยตรง
- CPU สามารถ map GPU memory ได้โดยตรงเพื่อทำการ initialize
- ข้อมูลขนาดใหญ่สามารถส่งผ่านคำสั่ง copy เพื่อให้เกิด การบีบอัด DCC และการจัดการ swizzle
- มีการแยก address ที่ CPU map ได้ออกจาก GPU address และแปลงผ่าน gpuHostToDevicePointer
การทำให้ข้อมูลและ shader language ทันสมัยขึ้น
- ใช้ shader language แบบพอยน์เตอร์ C/C++ เช่นเดียวกับ CUDA, Metal และ OpenCL
- สามารถเข้าถึงหน่วยความจำได้อย่างมีประสิทธิภาพด้วย wide load ระดับ struct (128 บิตขึ้นไป)
- ByteAddressBuffer หรือ texel buffer ของ DirectX ไม่ใช่ทางเลือกที่เหมาะที่สุดอีกต่อไป
- GLSL/HLSL ไม่รองรับพอยน์เตอร์ จึง ขาดระบบนิเวศ shader library ที่นำกลับมาใช้ซ้ำได้ ขณะที่ CUDA พัฒนาไปจนมีไลบรารีที่อุดมสมบูรณ์
Root argument และโครงสร้างข้อมูล
- GPU kernel รับอินพุตเป็น พอยน์เตอร์ 64 บิตเพียงตัวเดียว แล้ว cast เป็น struct
- CPU และ GPU ใช้ C/C++ header ชุดเดียวกันเพื่อคงความสอดคล้องของโครงสร้างข้อมูล
- ใช้คีย์เวิร์ด const/restrict เพื่อช่วยให้คอมไพเลอร์ optimize ได้ดีขึ้น และตัดการแยก UBO/SSBO ที่ไม่จำเป็น
- ใช้ประโยชน์จาก scalar register preloading และ dynamic uniform optimization ของ GPU ยุคใหม่
การทำให้ texture binding ง่ายขึ้น
- จัดการ texture ทั้งหมดผ่าน อาเรย์ descriptor 256 บิต (heap) ที่ CPU และ GPU เขียนได้โดยตรง
- รองรับการสุ่มตัวอย่าง texture แบบ non-uniform ด้วย การเข้าถึงผ่านดัชนี 32 บิต
- เรียบง่ายกว่า descriptor heap ของ DirectX 12 SM 6.6 และมีลักษณะคล้าย Vulkan VK_EXT_descriptor_buffer
- การสร้างออบเจ็กต์ texture การอัปโหลด และการ sample ถูกรวมเป็นรูปแบบเดียวที่อิงกับ GPU memory pointer
Shader pipeline และค่าคงที่
- การสร้าง pipeline ทำได้ง่ายเพียง โหลด shader IR แล้วเรียก gpuCreatePipeline
- ไม่ต้องมี root signature, descriptor set หรือการกำหนด binding
- ใช้ ค่าคงที่แบบ static (อิง struct) แทน shader specialization constant เพื่อลดปัญหา การระเบิดของชุดผสม PSO
- struct ของค่าคงที่สามารถมี GPU pointer อยู่ภายในได้ จึง hardcode runtime address ได้โดยตรง
การทำให้ barrier และ synchronization ง่ายขึ้น
- รายการ barrier แยกราย resource ของ API เดิมไม่สอดคล้องกับโครงสร้าง GPU ยุคใหม่
- โมเดลที่เสนอใช้เพียง bitfield flag ระดับคิว/สเตจ
- ทำให้ง่ายขึ้นเป็นรูป gpuBarrier(before, after, hazard) โดยไม่ต้องติดตาม resource
- ใช้คำสั่ง gpuSignalAfter / gpuWaitBefore เพื่อทำ GPU→GPU synchronization ที่คล้าย timeline semaphore
Command buffer และการเรนเดอร์
- ใช้เฉพาะ command buffer แบบใช้ครั้งเดียว (transient) และตัดโมเดลการนำกลับมาใช้ซ้ำที่ซับซ้อนของ Vulkan ออก
- ใช้ gpuBeginRenderPass / gpuEndRenderPass เพื่อกำหนด render target และล้างค่า
- ไม่มี barrier อัตโนมัติระหว่าง render pass จึงสามารถ optimize งานเรนเดอร์แบบขนานและ depth pre-pass ได้
การทำให้ raster pipeline ง่ายขึ้น
- vertex/pixel shader เข้าถึงข้อมูลแบบพอยน์เตอร์ จึงไม่ต้องมี binding API
- แยก GpuDepthStencilState, GpuBlendState ออกจาก PSO เพื่อลดจำนวนชุดผสม
- GPU บนมือถือรองรับ programmable blending ผ่าน framebuffer fetch intrinsic
- PSO จึงเก็บเฉพาะสถานะขั้นต่ำที่จำเป็น เช่น topology, format และจำนวน sample
Indirect draw และการเรนเดอร์ที่ขับเคลื่อนด้วย GPU
- ส่งอาร์กิวเมนต์ทั้งหมด (data, arguments) ผ่าน GPU pointer
- รองรับ multi-draw ด้วย gpuDrawIndexedInstancedIndirectMulti
- GPU สามารถสร้าง root data และ draw argument ได้เองโดยตรง ทำให้สร้าง GPU-driven rendering แบบสมบูรณ์ได้
Tooling และความเข้ากันได้
- โครงสร้างแบบพอยน์เตอร์สามารถติดตามได้ผ่านข้อมูล symbol เช่นเดียวกับ CUDA/Metal debugger
- ได้รับการป้องกันด้วย virtual memory จึงไม่มีปัญหาด้านความปลอดภัย และจะเกิด page fault เมื่อเข้าถึงผิดพลาด
- เช่นเดียวกับกรณีของ MoltenVK, Proton API เดิมอย่าง DirectX/Vulkan/Metal สามารถรองรับผ่านเลเยอร์แปลงได้
สเปกขั้นต่ำและบทสรุป
- Nvidia Turing(2018), AMD RDNA1(2019), Intel Xe1(2022), Apple M1(2020) ต่างก็รองรับความสามารถที่เสนอทั้งหมด
- GPU ยุคนี้เปลี่ยนไปเป็นสถาปัตยกรรมแบบ bindless, พอยน์เตอร์ 64 บิต, แคชที่สอดคล้องกัน แล้ว
- มีเพียง API เท่านั้นที่ยังติดอยู่กับ abstraction แบบเก่า
- API แบบใหม่จะ ง่ายกว่า DirectX 11, เร็วกว่า Vulkan และยืดหยุ่นกว่า Metal
- Vulkan 2.0 / DirectX 13 รุ่นถัดไปควรเปลี่ยนไปสู่ ดีไซน์ bindless แบบสมบูรณ์ และผลักดันการขยายระบบนิเวศด้วย shader language แบบพอยน์เตอร์ C/C++ แทน HLSL/GLSL
1 ความคิดเห็น
ความเห็นจาก Hacker News
บทความนี้แสดงให้เห็นได้อย่างยอดเยี่ยมว่าส่วนไหนของ Vulkan และ DX12 ที่ไม่จำเป็น
ทุกวันนี้ DX12 ดูเหมือนถูกปล่อยทิ้งไว้จนแทบไม่รองรับแม้แต่ buffer pointer แล้ว และ Vulkan เองก็ยังไม่ได้ถูกรวบให้เป็นระเบียบใน 2.0 เลยมีไดรเวอร์จำนวนมากที่ติดตั้ง ส่วนขยาย ได้ไม่สมบูรณ์
ถ้ามี API แบบใหม่นี้จริง ก็น่าจะจำลอง OpenGL บนมันได้เร็วขึ้นมาก และอะไรอย่าง SDL3 GPU ก็น่าจะได้ประสิทธิภาพเพิ่มขึ้น 3~4 เท่า
หนังสือของ Frank Luna ก็ไม่ได้ครอบคลุมฟีเจอร์ใหม่ ๆ ทั้งหมด ต้องไปไล่ดูทั้งใน Learn, ตัวอย่างบน GitHub และเอกสารอ้างอิง
Vulkan ก็ซับซ้อนเหมือนกัน และถึงจะมี 2.0 ออกมาจริง ก็ยังสงสัยว่าในแพลตฟอร์มสำคัญอย่าง Android จะใช้งานจริงอย่างไร
นอกจาก Intel Arc แล้ว GPU ส่วนใหญ่ก็ยังทำงานได้แม้ไม่มี reBAR แต่ดูเหมือน Microsoft หรือ Intel จะต้องบังคับใช้สิ่งนี้ผ่าน UEFI ก่อน ถึงจะใช้ฟีเจอร์อย่าง bindless texture ได้อย่างเสถียร
แต่ถ้าทำแบบนั้นก็จะมีเส้นขั้นต่ำของฮาร์ดแวร์ที่รองรับ และเมนบอร์ดก่อนปี 2020 ก็รองรับแบบไม่สม่ำเสมอ
บทความนี้ขาดแรงจูงใจหลักไป
ตามดัชนีของบล็อก ใจความคือ “ในช่วง 10 ปีที่ผ่านมา ความซับซ้อนของกราฟิกส์ API และภาษาชेडเดอร์เพิ่มขึ้นอย่างรวดเร็ว ตอนนี้จึงควร ทำให้ชั้น abstraction เรียบง่ายขึ้น เพื่อเพิ่มทั้งประสิทธิภาพการพัฒนาและประสิทธิภาพการทำงาน พร้อมรับมือกับ workload ของ GPU ในอนาคต”
ตอนแรก SSD ยังใช้ส่วนต่อประสาน IDE/SATA เดิมอยู่ แต่กว่าจะดึงประสิทธิภาพได้จริงก็ต้องทิ้งสมมติฐานแบบดิสก์หมุน แล้วออกแบบวิธีส่งถ่ายข้อมูลใหม่
กราฟิกส์ API เองก็ดูจะมาถึงจุดที่ต้องทิ้ง ข้อจำกัดแบบ legacy เช่นกัน
ผมอาจมีอคติอยู่บ้างเพราะติดตามงานของ Sebastian Aaltonen มานาน แต่บทความนี้ยอดเยี่ยมจริง ๆ
ผมคิดว่าทิศทางต่อจากนี้คือ การหวนกลับไปสู่ software rendering
เพียงแต่ครั้งนี้ต่างออกไปตรงที่อัลกอริทึมและโครงสร้างข้อมูลเหล่านั้นจะได้แรงเร่งจากฮาร์ดแวร์
ในวงการ VFX แนวโน้มแบบนี้เกิดขึ้นแล้ว และตัวอย่างหนึ่งคือเมื่อราว 5 ปีก่อน OTOY พอร์ต OctaneRender ไปอยู่บน CUDA
ประเภทของชेडเดอร์มีหน้าที่เชื่อมช่องว่างระหว่าง pipeline เหล่านี้ ดังนั้นการทำทุกอย่างให้เป็นซอฟต์แวร์ทั้งหมดจึงไม่สมจริงนัก
ตัวอย่างเช่น Nanite ของ Unreal Engine ใช้ software rasterizer ที่ทำงานด้วย compute shader ของ GPU เมื่อต้องจัดการสามเหลี่ยมขนาดเล็ก
รายละเอียดในบทความนี้น่าประทับใจมาก
แม้ผมจะเข้าใจเพียงบางส่วน แต่มันน่าจะกลายเป็นเอกสารอ้างอิงสำหรับ การออกแบบกราฟิกส์ API ในอนาคต
สำหรับเกมเมอร์ส่วนใหญ่ Vulkan/DX12 ไม่ได้ให้ประโยชน์มากนัก และหลายเกมก็มีปัญหาเพราะเรื่อง PSO
แม้ Vulkan จะกำลังปรับปรุงอยู่ แต่ WebGPU ก็ยังรับข้อจำกัดจากการออกแบบ Vulkan ยุคแรกมาด้วย
ในสถานการณ์ที่ฮาร์ดแวร์พัฒนาเร็วมาก การลงไปสู่ API ระดับต่ำเกินไปอาจเป็นความผิดพลาด
บางทีแนวทางแบบ CUDA ที่เน้นการประมวลผลทั่วไปอาจเป็นทิศทางที่ดีกว่า
ทำให้นึกถึง Mantle ขึ้นมา
แม้มันจะมีข้อเสีย แต่ก็ให้ความรู้สึกเหมือนได้จับฮาร์ดแวร์โดยตรง และตอนพัฒนา Xbox 360 นั้นสนุกที่สุดแล้ว
Nvidia กับ Nintendo เป็นผู้ออกแบบ และผมคิดว่ามันเป็น API ของคอนโซลที่เข้าใจง่ายที่สุด
อ่านบทความนี้แล้วรู้สึกเหมือนได้เห็น ช่วงเวลาทางประวัติศาสตร์
ทำให้นึกถึง Google Toucan ซึ่งดูเหมือนจะเป็นโปรเจกต์ที่เกี่ยวข้อง
บทความนี้ทำให้นึกถึงความทรงจำเก่า ๆ หลายอย่าง
ปัจจัยเพิ่มเติมที่นักออกแบบ API ต้องคำนึงถึงมี เช่น
สงสัยว่าทำไม Microsoft ถึงไม่ออก DirectX เวอร์ชัน ใหม่
DirectX Ultimate หรือ 12.2 เองก็แทบจะเหมือน DX12 เดิม
เป็นเพราะมิดเดิลแวร์อย่าง Unreal Engine ทำให้ความสำคัญของ API ของตัวเองลดลงหรือเปล่า หรือว่า EPIC ควรเป็นฝ่ายเสนอ API ใหม่
นักพัฒนาเกมตัวจริงจะสร้าง RHI (Rendering Hardware Interface) แล้วไปโฟกัสกับการทำเกม
Ray tracing กับ mesh shader เป็นนวัตกรรมที่ใหญ่ที่สุด แต่ก็ยังถูกใช้น้อย เลยดูเหมือนยังไปต่อไม่มากนัก
ความ รวมศูนย์ ของเอนจินอย่าง Unreal, Unity ทำให้ความสนใจต่อการสร้างนวัตกรรมใน API ลดลง และความก้าวหน้าจึงไปอยู่ฝั่ง GPU เป็นหลัก
API ฝั่ง CPU ยังอยู่แค่ระดับการแมปบัฟเฟอร์แบบเรียบง่าย
คงต้องมีการเปลี่ยนแปลงฮาร์ดแวร์ครั้งใหม่เหมือนตอน tessellation shader ปรากฏขึ้น ถึงจะเกิดความคืบหน้า
สงสัยว่า Valve จะมีโอกาสสร้างกราฟิกส์ API ของตัวเองสำหรับ SteamOS หรือไม่
เคยได้ยินมาว่าในช่วงพัฒนา Vulkan ระยะแรก Valve เป็นหนึ่งในผู้ผลักดันหลัก