เนื้อหาในเว็บไซต์นี้ได้รับการแปลโดยใช้ปัญญาประดิษฐ์ (AI) หรือเทคโนโลยีการแปลด้วยเครื่อง และอาจมีข้อผิดพลาด

Skip to content

3 ปีแห่งโลหะ

เมื่อสามปีที่แล้ว เราได้พอร์ตเรนเดอร์ของเราไปยัง Metal มันไม่ได้ใช้เวลามากนัก มันสนุกมาก และมันทำงานได้ดีมากบน iOS ดังนั้นเราจึงเขียนบทความที่อธิบายถึงวิธีที่เราตัดสินใจและผลลัพธ์ที่ได้ (สปอยล์: ดีมาก!) ส่วนใหญ่ของบทความย้อนหลังนั้นยังคงใช้ได้ แต่ปัจจุบัน Metal อยู่ในสภาพที่ดีกว่าที่เคย ดังนั้นเราจึงตัดสินใจเผยแพร่บทความนี้อีกครั้งพร้อมกับการอัปเดตสามปี

ดังนั้นเราย้อนเวลากลับไป ทำเหมือนว่าเป็นเดือนธันวาคมปี 2016 และเราเพิ่งส่งเวอร์ชันของ Metal renderer ของเราบน iOS ไป

ทำไมต้องเป็นโลหะ?

เมื่อ Apple ประกาศ Metal ที่งาน WWDC ในปี 2014 ปฏิกิริยาแรกของผมคือการเพิกเฉยต่อมัน มันมีให้ใช้เฉพาะบนฮาร์ดแวร์รุ่นใหม่ล่าสุดซึ่งผู้ใช้ส่วนใหญ่ของเราไม่มี และแม้ว่า Apple จะบอกว่ามันแก้ปัญหาประสิทธิภาพของ CPU ได้ แต่การปรับให้เหมาะสมกับตลาดที่เล็กที่สุดจะหมายถึงช่องว่างระหว่างอุปกรณ์ที่เร็วที่สุดและช้าที่สุดจะยิ่งกว้างขึ้นอีก ในขณะนั้นเราใช้ OpenGL ES 2 เท่านั้นบน Apple และกำลังเริ่มพอร์ตไปยัง Android ด้วย

ข้ามไปข้างหน้าสองปีครึ่ง นี่คือภาพรวมส่วนแบ่งตลาดของโลหะสำหรับผู้ใช้ของเรา:

นี่ดูน่าสนใจกว่าที่เคยเป็นมาอย่างมาก ถึงแม้ว่าการนำ Metal มาใช้จะไม่ได้ช่วยอุปกรณ์รุ่นเก่าที่สุด แต่ก็ต้องยอมรับว่าตลาด GL บน iOS กำลังหดตัวลงเรื่อยๆ และเนื้อหาที่เราใช้งานบนอุปกรณ์เก่าเหล่านี้ก็มักจะแตกต่างจากเนื้อหาที่รันบนอุปกรณ์รุ่นใหม่ ดังนั้นจึงสมเหตุสมผลที่จะทุ่มเทความพยายามบางส่วนเพื่อทำให้มันเร็วขึ้น เนื่องจากโค้ด Metal ของคุณบน iOS สามารถทำงานบน Mac ได้โดยมีการเปลี่ยนแปลงเพียงเล็กน้อย จึงอาจสมเหตุสมผลที่จะใช้โค้ดนี้บน Mac ด้วย แม้ว่าคุณจะมุ่งเน้นไปที่การพัฒนาบนมือถือเป็นหลัก (ปัจจุบันเราจัดส่งเฉพาะเวอร์ชัน Metal บน iOS เท่านั้น)

ผมคิดว่าการวิเคราะห์ส่วนแบ่งตลาดในรายละเอียดเพิ่มเติมนั้นคุ้มค่า บน iOS เราสนับสนุน Metal สำหรับ iOS 8.3 ขึ้นไป; แม้ว่าจะมีผู้ใช้บางรายที่ไม่สามารถใช้งาน Metal ได้เนื่องจากข้อจำกัดของเวอร์ชันระบบปฏิบัติการ แต่ส่วนใหญ่ของ 25% ที่ยังคงใช้ GL อยู่ ก็เป็นเพียงผู้ใช้ที่ใช้อุปกรณ์รุ่นเก่าซึ่งมีฮาร์ดแวร์ SGX เท่านั้น พวกเขายังไม่มีฟีเจอร์ OpenGL ES 3 ใดๆ และเราพอใจกับการใช้เส้นทางการเรนเดอร์ระดับล่างกว่าในที่นั้น (แม้ว่าเราจะอยากให้ทุกอุปกรณ์ใช้ Metal - โชคดีที่การแยก GL/Metal จะช่วยปรับปรุงให้ดีขึ้นเท่านั้น) บน Mac, Metal API เป็นเวอร์ชันใหม่กว่าและระบบปฏิบัติการมีบทบาทสำคัญมาก - คุณต้องใช้ OSX 10.11 ขึ้นไปเพื่อใช้ Metal และผู้ใช้ครึ่งหนึ่งของเราใช้ระบบปฏิบัติการที่เก่ากว่า - มันเกี่ยวกับซอฟต์แวร์มากกว่าฮาร์ดแวร์ (95% ของผู้ใช้ Mac ของเราใช้ OpenGL 3.2 ขึ้นไป)

ดังนั้นเมื่อพิจารณาส่วนแบ่งการตลาดแล้ว เรายังมีทางเลือกที่ไม่จำเป็นต้องพอร์ตไปยัง Metal หนึ่งในนั้นคือการใช้ MoltenGL ซึ่งจะใช้โค้ด OpenGL ที่เรามีอยู่แล้ว แต่คาดว่าจะเร็วกว่า อีกทางเลือกหนึ่งคือการพอร์ตไปยัง Vulkan (เพื่อให้ได้ประสิทธิภาพที่ดีขึ้นบน PC และในที่สุดก็บน Android) และใช้ MoltenVK ผมได้ประเมิน MoltenGL อย่างคร่าว ๆ แล้ว และไม่ค่อยประทับใจกับผลลัพธ์เท่าไรนัก - ต้องใช้ความพยายามพอสมควรเพื่อให้โค้ดของเราสามารถทำงานได้เลย และแม้ว่าประสิทธิภาพจะดีกว่า OpenGL แบบมาตรฐานอยู่บ้าง แต่ผมก็คาดหวังไว้มากกว่านี้ สำหรับ MoltenVK ผมคิดว่าการพยายามนำ API ระดับต่ำหนึ่งตัวมาใช้เป็นชั้นบนอีกตัวหนึ่งนั้นเป็นการเข้าใจผิด - คุณจะต้องเจอปัญหาความไม่เข้ากันของระบบซึ่งจะส่งผลให้ประสิทธิภาพไม่ดีเท่าที่ควร - อาจจะดีกว่า API ระดับสูงที่คุณเคยใช้ แต่มันไม่น่าจะเร็วที่สุดเท่าที่เป็นไปได้ ซึ่งนั่นน่าจะเป็นเหตุผลที่คุณเลือกใช้ API ระดับต่ำตั้งแต่แรก! อีกประเด็นสำคัญหนึ่งคือการนำ Metal ไปใช้งานนั้นง่ายกว่า Vulkan มาก - จะกล่าวถึงเพิ่มเติมในภายหลัง - ดังนั้นในบางแง่มุม ผมจึงชอบการใช้ตัวห่อหุ้ม Metal -> Vulkan มากกว่า Vulkan -> Metal

นอกจากนี้ยังควรสังเกตว่า บน iOS 10 ใน iPhone รุ่นใหม่ล่าสุดนั้น ไม่มีไดรเวอร์ GL - GL ถูกนำไปใช้งานบน Metal ซึ่งหมายความว่า การใช้ OpenGL จะช่วยประหยัดความพยายามในการพัฒนาได้เพียงเล็กน้อยเท่านั้น - ไม่มากนัก เมื่อพิจารณาถึงคำสัญญาของ "เขียนครั้งเดียว ใช้งานได้ทุกที่" ที่ OpenGL มี ซึ่งในความเป็นจริงแล้วไม่ได้ผลดีนักบนอุปกรณ์มือถือ

การพอร์ต

โดยรวมแล้ว การพอร์ตไปยัง Metal นั้นง่ายดายมาก เรามีประสบการณ์มากมายในการทำงานกับ API กราฟิกต่างๆ ตั้งแต่ API ระดับสูงอย่าง Direct3D 9/11 ไปจนถึง API ระดับต่ำอย่าง PS4 GNM ซึ่งทำให้เรามีข้อได้เปรียบที่ไม่เหมือนใครในการสามารถใช้ API อย่าง Metal ได้อย่างสบายๆ ซึ่ง API นี้อยู่ในระดับสูงพอสมควรแต่ก็ยังคงปล่อยให้งานบางอย่าง เช่น การซิงโครไนซ์ระหว่าง CPU และ GPU ให้ผู้พัฒนาแอปเป็นผู้จัดการเอง

อุปสรรคเดียวจริงๆ คือการทำให้เชดเดอร์ของเราคอมไพล์ได้ - เมื่อทำเสร็จแล้วและถึงเวลาเขียนโค้ด ก็เห็นได้ชัดว่า API นั้นเรียบง่ายและอธิบายตัวเองได้ดีมากจนโค้ดแทบจะเขียนได้เอง ผมสามารถทำให้พอร์ตที่แสดงผลส่วนใหญ่ได้ไม่ค่อยดีนักทำงานได้ในเวลาประมาณ 10 ชั่วโมงในวันเดียว และใช้เวลาอีกสองสัปดาห์ในการทำความสะอาดโค้ด แก้ไขปัญหาการตรวจสอบความถูกต้อง การวิเคราะห์และปรับแต่งประสิทธิภาพ รวมถึงการขัดเกลาทั่วไป การที่สามารถสร้างการใช้งาน API ได้ในช่วงเวลานี้แสดงให้เห็นถึงคุณภาพของ API และชุดเครื่องมือได้เป็นอย่างดี ผมเชื่อว่ามีส่วนประกอบหลายประการที่มีส่วนช่วย:

  • คุณสามารถพัฒนาโค้ดได้เป็นขั้นตอน โดยมีการให้ข้อมูลย้อนกลับที่ดีในทุกขั้นตอน โค้ดของเราเริ่มต้นด้วยการไม่สนใจการซิงโครไนซ์ระหว่าง CPU และ GPU เลย ซึ่งทำให้บางส่วนของการตั้งค่าสถานะไม่ดีนัก ใช้การติดตามทรัพยากรแบบอ้างอิงในตัว และไม่เคยรัน CPU และ GPU พร้อมกันเพื่อหลีกเลี่ยงปัญหาต่าง ๆ ขั้นตอนการปรับปรุงประสิทธิภาพ/การขัดเกลาจึงได้เปลี่ยนโค้ดนี้ให้กลายเป็นสิ่งที่เราสามารถนำไปใช้ได้ โดยไม่สูญเสียความสามารถในการเรนเดอร์ในกระบวนการนี้
  • เครื่องมือเหล่านี้มีให้คุณแล้ว พวกมันทำงานได้ดีและทำงานได้ดีมาก นี่อาจไม่ใช่เรื่องน่าแปลกใจสำหรับผู้คนที่คุ้นเคยกับ Direct3D 11 - แต่นี่เป็นครั้งแรกบนมือถือที่ฉันมีโปรไฟล์เลอร์ CPU, โปรไฟล์เลอร์ GPU, ดีบักเกอร์ GPU และชั้นตรวจสอบความถูกต้องของ API GPU ที่ทำงานร่วมกันได้ดี ตรวจจับปัญหาส่วนใหญ่ในระหว่างการพัฒนา และช่วยปรับปรุงประสิทธิภาพของโค้ด
  • แม้ว่า API จะมีระดับต่ำกว่า Direct3D 11 อยู่บ้าง และปล่อยให้ผู้พัฒนาตัดสินใจในบางเรื่องที่สำคัญในระดับต่ำ (เช่น การกำหนดค่า render pass หรือการซิงโครไนซ์) แต่ก็ยังคงใช้โมเดลทรัพยากรแบบดั้งเดิมที่แต่ละทรัพยากรมี "flag การใช้งาน" ที่ถูกสร้างขึ้นมาด้วย แต่ไม่จำเป็นต้องใช้ pipeline barriers หรือการเปลี่ยนรูปแบบการจัดวาง และใช้โมเดลการผูกแบบดั้งเดิมที่แต่ละขั้นตอนของ shader มีช่องว่างหลายช่องที่คุณสามารถกำหนดทรัพยากรได้อย่างอิสระ ทั้งสองอย่างนี้คุ้นเคย เข้าใจง่าย และต้องการโค้ดเพียงเล็กน้อยเพื่อให้เริ่มต้นได้อย่างรวดเร็ว

อีกสิ่งหนึ่งที่ช่วยได้คือ อินเทอร์เฟซ API ของเราพร้อมสำหรับ API แบบ Metal แล้ว - มันมีความเรียบง่ายมากแต่เปิดเผยรายละเอียดเพียงพอ (เช่น การผ่านเรนเดอร์) เพื่อให้สามารถเขียนการใช้งานที่มีประสิทธิภาพได้อย่างง่ายดาย ในระหว่างการดำเนินการของเรา ไม่มีจุดใดเลยที่ฉันจำเป็นต้องบันทึก/กู้คืนสถานะ (หลายอินเทอร์เฟซของ API มีปัญหานี้ โดยเฉพาะอย่างยิ่งเนื่องจากการจัดการการตั้งค่าเป้าหมายการแสดงผลเป็นการเปลี่ยนแปลงสถานะและการผูกทรัพยากร/สถานะที่คงอยู่ผ่านสิ่งนั้น) หรือทำการตัดสินใจที่ซับซ้อนเกี่ยวกับอายุการใช้งานของทรัพยากร/การซิงโครไนซ์ เกี่ยวกับโค้ดที่ "ซับซ้อน" เพียงอย่างเดียวที่จำเป็นสำหรับการเรนเดอร์ คือโค้ดที่สร้างสถานะของเรนเดอร์ไพพ์ไลน์โดยการแฮชบิตที่จำเป็นในการสร้างสถานะนั้น - วัตถุสถานะของไพพ์ไลน์ไม่ได้เป็นส่วนหนึ่งของการนามธรรม API ของเรา แม้แต่ส่วนนั้นก็ค่อนข้างตรงไปตรงมาและรวดเร็ว ผมจะเขียนเพิ่มเติมเกี่ยวกับอินเตอร์เฟซ API ของเราในโพสต์แยกต่างหาก

ดังนั้น หนึ่งสัปดาห์สำหรับการคอมไพล์เชดเดอร์ สองสัปดาห์สำหรับการปรับแต่งและเพิ่มประสิทธิภาพการใช้งาน1 - ผลลัพธ์เป็นอย่างไร? ผลลัพธ์ยอดเยี่ยม - Metal มอบประสิทธิภาพตามที่สัญญาไว้อย่างแน่นอน ประการหนึ่ง ประสิทธิภาพการประมวลผลแบบเธรดเดียว (single threaded dispatch) นั้นดีขึ้นอย่างเห็นได้ชัดเมื่อเทียบกับ OpenGL (โดยส่วนของการส่งคำสั่งวาดภาพในเฟรมเรนเดอร์ของเราลดลง 2-3 เท่า ขึ้นอยู่กับปริมาณงาน) ทั้งที่การปรับแต่ง OpenGL ของเราเองก็ถือว่าทำได้ดีแล้วในแง่ของการลดการตั้งค่าสถานะที่ซ้ำซ้อนและทำงานร่วมกับไดรเวอร์ได้อย่างราบรื่นโดยใช้เส้นทางที่รวดเร็ว อย่างไรก็ตาม เรื่องนี้ยังไม่จบเพียงเท่านี้—การประมวลผลแบบหลายเธรด (multithreading) ใน Metal นั้นสามารถนำไปใช้ได้อย่างง่ายดาย หากโค้ดการเรนเดอร์ของคุณพร้อมรองรับ เรายังไม่ได้เปลี่ยนไปใช้การส่งงานแบบแยกเธรด (threaded draw dispatch) แต่กำลังอยู่ในขั้นตอนการแปลงส่วนอื่น ๆ ที่เตรียมทรัพยากรให้ทำงานนอกเธรดการเรนเดอร์ ซึ่งแตกต่างจาก OpenGL ตรงที่กระบวนการนี้แทบไม่ต้องใช้ความพยายามเลย

นอกเหนือจากนั้น Metal ยังช่วยให้เราแก้ไขปัญหาด้านประสิทธิภาพอื่นๆ ได้โดยให้เครื่องมือที่เข้าถึงได้ง่ายและเชื่อถือได้ หนึ่งในส่วนสำคัญของโค้ดการเรนเดอร์ของเราคือระบบที่คำนวณข้อมูลแสงบน CPU ในพื้นที่โลกและอัปโหลดไปยังพื้นที่ของเท็กซ์เจอร์ 3 มิติ (ซึ่งเราต้องจำลองบนฮาร์ดแวร์ OpenGL ES 2) การอัปเดตเป็นเพียงบางส่วนเท่านั้น ดังนั้นเราจึงไม่สามารถทำซ้ำพื้นผิวทั้งหมดได้และต้องพึ่งพาวิธีการที่ไดรเวอร์ใช้กับ glTexSubImage3D ในบางจุดเราพยายามใช้ PBO เพื่อปรับปรุงประสิทธิภาพการอัปเดต แต่พบปัญหาความเสถียรอย่างมากในทุกด้าน ทั้งบน Android และ iOS บน Metal มีสองวิธีในตัวสำหรับอัปโหลดภูมิภาค - MTLTexture.replaceRegion ซึ่งคุณสามารถใช้ได้หาก GPU ไม่ได้อ่านเท็กซ์เจอร์อยู่ในขณะนี้ หรือ MTLBlitCommandEncoder (copyFromBufferToTexture หรือ copyFromTextureToTexture) ที่สามารถอัปโหลดภูมิภาคแบบอะซิงโครนัสได้ทันเวลาพอดีที่ GPU จะเริ่มใช้เท็กซ์เจอร์

ทั้งสองวิธีนี้ช้ากว่าที่ผมต้องการ - วิธีแรกแทบจะไม่สามารถใช้ได้เลยเนื่องจากเราต้องรองรับการอัปเดตบางส่วนอย่างมีประสิทธิภาพ และมันทำงานบน CPU เท่านั้นโดยใช้สิ่งที่ดูเหมือนการแปลที่อยู่ซึ่งช้ามาก วิธีที่สองใช้งานได้แต่ดูเหมือนจะใช้การบลิท 2D หลายครั้งเพื่อเติมเท็กซ์เจอร์ 3D ซึ่งทั้งการตั้งค่าคำสั่งบนฝั่ง CPU มีค่าใช้จ่ายสูงมาก และยังมีการใช้ทรัพยากร GPU สูงมากด้วยเหตุผลบางอย่าง หากนี่เป็น OpenGL เรื่องนี้คงจบไปแล้ว - ในความเป็นจริง ประสิทธิภาพของทั้งสองวิธีนี้ใกล้เคียงกับต้นทุนที่สังเกตได้จากการอัปเดตที่คล้ายกันใน OpenGL อย่างมาก โชคดีที่นี่คือ Metal ซึ่งสามารถเข้าถึงคอมไพร์เชเดอร์ได้อย่างง่ายดาย - และคอมไพร์เชเดอร์ที่ง่ายมาก ๆ ก็มอบความสามารถให้เราสามารถทำการอัปโหลดบัฟเฟอร์เป็นเท็กซ์เจอร์ 3D ได้อย่างรวดเร็วมากทั้งบน CPU และ GPU และแก้ไขปัญหาด้านประสิทธิภาพในส่วนนี้ของโค้ดของเราได้เป็นอย่างดี

ในฐานะความคิดเห็นทั่วไปสุดท้าย การรักษาโค้ด Metal นั้นแทบไม่ต้องใช้ความพยายามเลย - คุณสมบัติเพิ่มเติมทั้งหมดที่เราต้องเพิ่มจนถึงตอนนี้ก็ง่ายกว่าที่จะเพิ่มในโค้ด Metal มากกว่า API อื่น ๆ ที่เราสนับสนุน และผมคาดว่าแนวโน้มนี้จะยังคงดำเนินต่อไป มีความกังวลเล็กน้อยว่าการเพิ่ม API อีกหนึ่งตัวจะต้องมีการบำรุงรักษาอย่างต่อเนื่อง แต่เมื่อเปรียบเทียบกับ OpenGL แล้ว สิ่งนี้ไม่ต้องการงานมากนัก ในความเป็นจริง เนื่องจากเราไม่จำเป็นต้องรองรับ OpenGL ES 3 บน iOS อีกต่อไป นั่นหมายความว่าเราสามารถทำให้โค้ด OpenGL บางส่วนที่เราใช้อยู่ให้เรียบง่ายขึ้นได้อีกด้วย

ความมั่นคง

วันนี้บน iOS Metal รู้สึกเสถียรมาก ผมไม่แน่ใจว่าสถานการณ์เป็นอย่างไรตอนเปิดตัวในปี 2014 หรือเป็นอย่างไรบน Mac ในปัจจุบัน แต่ทั้งไดรเวอร์และเครื่องมือสำหรับ iOS รู้สึกค่อนข้างมั่นคง

เรามีปัญหาเกี่ยวกับไดรเวอร์หนึ่งกรณีบน iOS 10 ที่เกี่ยวข้องกับการโหลดเชดเดอร์ที่คอมไพล์ด้วย Xcode 7 (ซึ่งเราได้แก้ไขโดยการเปลี่ยนไปใช้ Xcode 8) และปัญหาไดรเวอร์ล่มหนึ่งกรณีบน iOS 9 ซึ่งปรากฏว่าเกิดจากการใช้งาน API nextDrawable ไม่ถูกต้อง นอกเหนือจากนี้ เราไม่พบข้อบกพร่องด้านพฤติกรรมหรือการล่มใดๆ - สำหรับ API ที่ค่อนข้างใหม่ Metal ถือว่ามีความเสถียรมากในทุกด้าน

นอกจากนี้ เครื่องมือที่คุณได้รับพร้อมกับ Metal มีความหลากหลายและอุดมสมบูรณ์; โดยเฉพาะอย่างยิ่ง คุณสามารถใช้:

  • เลเยอร์การตรวจสอบความถูกต้องที่ครอบคลุมมาก ซึ่งจะระบุปัญหาทั่วไปในการใช้ API โดยพื้นฐานแล้วมันเหมือนกับการดีบัก Direct3D - ซึ่งคุ้นเคยสำหรับ Direct3D แต่แทบไม่เคยได้ยินใน OpenGL (ในทางทฤษฎี ARB_debug_callback ควรจะแก้ปัญหานี้ได้ แต่ในทางปฏิบัติมันมักจะไม่พร้อมใช้งาน และเมื่อใช้งานได้ก็ไม่ค่อยมีประโยชน์)
  • ตัวดีบัก GPU ที่ใช้งานได้ซึ่งแสดงคำสั่งทั้งหมดที่คุณได้ส่งไปพร้อมกับสถานะของมัน, เนื้อหาของเป้าหมายการレンเดอร์, เนื้อหาของเท็กซ์เจอร์, เป็นต้น ฉันไม่ทราบว่ามันมีตัวดีบักเชดเดอร์ที่ทำงานได้หรือไม่ เพราะฉันไม่เคยต้องการมัน และการตรวจสอบบัฟเฟอร์อาจง่ายขึ้นนิดหน่อย แต่ส่วนใหญ่แล้วมันทำงานได้ดี
  • โปรไฟล์ GPU ที่ทำงานได้ซึ่งแสดงสถิติประสิทธิภาพต่อรอบ (เวลา, แบนด์วิดท์) และเวลาการประมวลผลต่อเชดเดอร์ด้วย เนื่องจาก GPU เป็นแบบไทเลอร์ คุณจึงไม่สามารถคาดหวังเวลาการเรียกใช้แต่ละครั้งได้แน่นอน การมีระดับการมองเห็นนี้ - โดยเฉพาะเมื่อพิจารณาถึงการขาดข้อมูลการจับเวลา GPU อย่างสมบูรณ์ใน API กราฟิกบน iOS - ถือว่ายอดเยี่ยมมาก
  • การติดตามไทม์ไลน์การทำงานของ CPU/GPU (Metal System Trace) ที่แสดงการจัดตารางงานการประมวลผลของ CPU และ GPU ซึ่งคล้ายกับ GPUView แต่ใช้งานง่ายกว่ามาก ยกเว้นบางจุดที่อินเทอร์เฟซผู้ใช้มีความเฉพาะตัว
  • คอมไพเลอร์เชดเดอร์แบบออฟไลน์ที่ตรวจสอบไวยากรณ์เชดเดอร์ของคุณ ยืนยันความถูกต้อง และบางครั้งจะให้คำเตือนที่เป็นประโยชน์ แปลงเชดเดอร์ของคุณเป็นบิตไบนารีที่โหลดได้รวดเร็วในขณะรันไทม์ และมีการปรับแต่งประสิทธิภาพล่วงหน้าในระดับที่เหมาะสม ช่วยลดเวลาในการโหลด เนื่องจากคอมไพเลอร์ของไดรเวอร์อาจทำงานได้เร็วกว่า

หากคุณมาจากโลกของ Direct3D หรือคอนโซล คุณอาจมองข้ามสิ่งเหล่านี้ไปทั้งหมด - เชื่อฉันเถอะ ใน OpenGL ทุกสิ่งทุกอย่างเหล่านี้ล้วนเป็นสิ่งที่ไม่ธรรมดาและได้รับการต้อนรับด้วยความตื่นเต้น โดยเฉพาะบนมือถือที่คุณคุ้นเคยกับการจัดการกับไดร์เวอร์ที่บางครั้งอาจเสียหาย, ไม่มีระบบตรวจสอบ, ไม่มีตัวแก้ไขข้อผิดพลาดของ GPU, ไม่มีตัววิเคราะห์ประสิทธิภาพของ GPU ที่มีประโยชน์, ไม่มีความสามารถในการรวบรวมข้อมูลการจัดตารางเวลาของ GPU และถูกบังคับให้ทำงานกับภาษาเชดเดอร์ที่เป็นข้อความซึ่งแต่ละผู้ผลิตมีตัววิเคราะห์ที่แตกต่างกันเล็กน้อย

Metal เป็น API ที่ยอดเยี่ยมทั้งสำหรับการเขียนโค้ดและการพัฒนาแอปพลิเคชัน มันใช้งานง่าย มีประสิทธิภาพที่คาดการณ์ได้ มีไดรเวอร์ที่แข็งแกร่งและชุดเครื่องมือที่มั่นคง มันดีกว่า OpenGL ในทุก ๆ ด้าน ยกเว้นเรื่องความสามารถในการพกพา แต่ความจริงเกี่ยวกับ OpenGL คือคุณควรใช้มันแค่สามแพลตฟอร์มเท่านั้น (iOS, Android และ Mac) และสองในสามแพลตฟอร์มนั้นตอนนี้รองรับ Metal แล้ว นอกจากนี้ คำสัญญาเรื่องความสามารถในการพกพาของ OpenGL ก็ไม่ได้เป็นจริงอย่างที่คิด เพราะโค้ดที่คุณเขียนบนแพลตฟอร์มหนึ่ง มักจะไม่สามารถใช้งานบนอีกแพลตฟอร์มหนึ่งได้ด้วยเหตุผลที่แตกต่างกัน

หากคุณกำลังใช้เอนจินของบุคคลที่สามเช่น Unity หรือ UE4 Metal ได้รับการสนับสนุนอยู่แล้ว; หากคุณไม่ได้ใช้และคุณชอบการเขียนโปรแกรมกราฟิกหรือให้ความสำคัญกับประสิทธิภาพและจริงจังกับ iOS หรือ Mac ฉันขอแนะนำอย่างยิ่งให้คุณลองใช้ Metal ดู คุณจะไม่มีวันผิดหวัง

โลหะในปัจจุบัน

การเปลี่ยนแปลงที่ใหญ่ที่สุดที่เกิดขึ้นกับดนตรีเมทัลจากมุมมองของเราในช่วงสามปีที่ผ่านมาคือการยอมรับในระดับที่กว้างขวางอย่างมาก

เมื่อสามปีที่แล้ว หนึ่งในสี่ของอุปกรณ์ต้องใช้ OpenGL. ในปัจจุบัน สำหรับผู้ชมของเรา ตัวเลขนี้อยู่ที่ประมาณ 2% - ซึ่งหมายความว่า ระบบรองรับ OpenGL ของเราแทบไม่มีความสำคัญอีกต่อไป. เราอาจยังคงรักษาไว้ แต่สิ่งนี้จะไม่ดำเนินต่อไปนาน.

ไดรเวอร์ก็ดีขึ้นกว่าเดิม - โดยทั่วไปแล้วเราไม่พบปัญหาเกี่ยวกับไดรเวอร์บน iOS และเมื่อเราพบปัญหา พวกมันมักเกิดขึ้นบนต้นแบบแรก ๆ และเมื่อต้นแบบเหล่านั้นถูกนำไปผลิตจริง ปัญหาเหล่านั้นก็มักจะได้รับการแก้ไขแล้ว

เราได้ใช้เวลาบางส่วนในการปรับปรุงระบบ Metal backend ของเรา โดยมุ่งเน้นไปที่สามด้าน:

การปรับปรุงเครื่องมือคอมไพล์เชดเดอร์ใหม่

สิ่งหนึ่งที่เกิดขึ้นในช่วงสามปีที่ผ่านมาคือการเปิดตัวและพัฒนา Vulkan แม้ว่าดูเหมือนว่า API จะแตกต่างกันโดยสิ้นเชิง (และมันเป็นเช่นนั้น) แต่ระบบนิเวศของ Vulkan ได้มอบชุดเครื่องมือโอเพนซอร์สที่ยอดเยี่ยมให้กับชุมชนด้านการเรนเดอร์ ซึ่งเมื่อรวมกันแล้วจะกลายเป็นชุดเครื่องมือสำหรับการคอมไพล์ที่มีคุณภาพระดับการผลิตที่ใช้งานง่าย

เราใช้ไลบรารีเพื่อสร้างเครื่องมือคอมไพล์ที่สามารถรับโค้ดต้นฉบับ HLSL (โดยใช้คุณสมบัติ DX11 ต่างๆ รวมถึงคอมไพเลอร์เชดเดอร์) คอมไพล์เป็น SPIRV ปรับแต่ง SPIRV ที่ได้ และแปลง SPIRV ที่ได้เป็น MSL (Metal Shading Language) มันแทนที่เครื่องมือคอมไพล์ของเราที่เคยใช้ได้เพียงโค้ดต้นฉบับ HLSL DX9 เป็นอินพุต และมีปัญหาความถูกต้องต่างๆ สำหรับเชดเดอร์ที่ซับซ้อน

มันค่อนข้างน่าขันที่แอปเปิลไม่ได้มีส่วนเกี่ยวข้องกับเรื่องนี้เลย แต่เราก็มาถึงจุดนี้แล้ว ขอขอบคุณอย่างยิ่งแก่ผู้ร่วมสนับสนุนและผู้ดูแล glslang (https://github.com/KhronosGroup/glslang), spirv-opt (https://github.com/KhronosGroup/SPIRV-Tools) และ SPIRV-Cross (https://github.com/KhronosGroup/SPIRV-Cross) เราได้มีส่วนร่วมในการส่งชุดแพตช์ให้กับไลบรารีเหล่านี้เพื่อช่วยให้เราสามารถจัดส่งชุดเครื่องมือใหม่ได้เช่นกัน และใช้มันในการเปลี่ยนเป้าหมายของเชดเดอร์ของเราไปยัง Vulkan, Metal และ OpenGL APIs

การสนับสนุน macOS

การพอร์ตไปยัง macOS เป็นความเป็นไปได้เสมอ แต่ไม่ใช่จุดสนใจหลักของเราจนกระทั่งเราเริ่มขาดคุณสมบัติบางอย่างไป ดังนั้นเราจึงตัดสินใจว่าเราควรลงทุนใน Metal บน macOS เพื่อให้ได้การเรนเดอร์ที่เร็วขึ้นและปลดล็อกความเป็นไปได้บางอย่างสำหรับอนาคต

จากมุมมองของการนำไปใช้, นี่ไม่ได้ยากเลย. ส่วนใหญ่ของ API นั้นเหมือนกันอย่างสมบูรณ์; นอกเหนือจากการจัดการหน้าต่าง, บริเวณเดียวที่ต้องการการปรับเปลี่ยนอย่างมากคือการจัดสรรหน่วยความจำ. บนมือถือ, มีพื้นที่หน่วยความจำที่ใช้ร่วมกันสำหรับบัฟเฟอร์และเท็กซ์เจอร์ ในขณะที่บนเดสก์ท็อป, API คาดคะเนว่ามี GPU ที่ใช้โดยเฉพาะพร้อมหน่วยความจำวิดีโอของตัวเอง.

เป็นไปได้ที่จะแก้ไขปัญหานี้อย่างรวดเร็วโดยใช้ทรัพยากรที่มีการจัดการ ซึ่ง Metal runtime จะดูแลการคัดลอกข้อมูลให้คุณ นี่เป็นวิธีที่เราใช้ในเวอร์ชันแรกของเรา แต่ต่อมาเราได้ปรับปรุงการดำเนินการใหม่เพื่อให้การคัดลอกข้อมูลทรัพยากรชัดเจนยิ่งขึ้นโดยใช้บัฟเฟอร์ชั่วคราว เพื่อที่เราจะสามารถลดภาระหน่วยความจำของระบบให้น้อยที่สุด

ความแตกต่างที่ใหญ่ที่สุดระหว่าง macOS และ iOS คือความเสถียร. บน iOS เราต้องรับมือกับผู้จัดหาไดร์เวอร์เพียงรายเดียวบนสถาปัตยกรรมเดียว ขณะที่บน macOS เราต้องรองรับผู้จัดหาไดร์เวอร์ทั้งสามราย (Intel, AMD, NVidia). นอกจากนี้ บน iOS เรา - โชคดี! - ได้ข้ามเวอร์ชัน *แรก* ของ iOS ที่มี Metal ให้ใช้ คือ iOS 8 ไป และบน macOS นี่ไม่สามารถทำได้ เพราะเราจะมีผู้ใช้ที่น้อยเกินไปที่จะใช้ Metal ในตอนนั้น ด้วยสาเหตุจากการรวมกันของปัญหาเหล่านี้ เราได้พบกับปัญหาเกี่ยวกับไดร์เวอร์มากขึ้นในทั้งส่วนที่ไม่ค่อยมีผลกระทบและส่วนที่ไม่ค่อยเป็นที่รู้จักของ API บน macOS

เรายังคงสนับสนุน macOS Metal ทุกเวอร์ชัน (10.11+) แม้ว่าจะเริ่มยกเลิกการสนับสนุนและเปลี่ยนไปใช้ OpenGL แบบเก่าสำหรับบางเวอร์ชันที่มีบั๊กในตัวคอมไพเลอร์เชดเดอร์ซึ่งยากต่อการแก้ไข เช่น บน 10.11 เราจำเป็นต้องใช้ macOS 10.11.6 ขึ้นไปเพื่อให้ Metal ทำงานได้

ประโยชน์ด้านประสิทธิภาพเป็นไปตามที่เราคาดหวังไว้; ในแง่ของส่วนแบ่งตลาด ปัจจุบันเรามีผู้ใช้ OpenGL ประมาณ 25% และผู้ใช้ Metal ประมาณ 75% บนแพลตฟอร์ม macOS ซึ่งถือว่าเป็นการแบ่งที่ค่อนข้างสมดุล นี่หมายความว่าในอนาคตเราอาจสามารถหยุดสนับสนุน OpenGL สำหรับเดสก์ท็อปได้อย่างสมบูรณ์ เนื่องจากไม่มีแพลตฟอร์มอื่นที่เราสนับสนุนใช้ OpenGL อีกแล้ว ซึ่งถือเป็นเรื่องดีในแง่ของการที่เราสามารถมุ่งเน้นไปที่ API ที่ง่ายต่อการสนับสนุนและให้ประสิทธิภาพที่ดีได้

การปรับปรุงประสิทธิภาพและการใช้หน่วยความจำ

เราค่อนข้างอนุรักษ์นิยมในประวัติศาสตร์กับคุณสมบัติของกราฟิก API ที่เราใช้ และ Metal ก็ไม่ใช่ข้อยกเว้น มีการอัปเดตคุณสมบัติใหญ่หลายอย่างที่ Metal ได้รับมาตลอดหลายปีที่ผ่านมา รวมถึง API การจัดสรรทรัพยากรที่ดีขึ้นพร้อม heaps ที่ชัดเจน, tile shaders กับ Metal 2, argument buffers และการสร้างคำสั่งทางฝั่ง GPU เป็นต้น

เราไม่ได้ใช้ฟีเจอร์ใหม่ๆ ส่วนใหญ่เลย จนถึงตอนนี้ ประสิทธิภาพก็อยู่ในระดับที่พอใช้ได้ และเราต้องการมุ่งเน้นไปที่การปรับปรุงที่สามารถนำไปใช้ได้ทั่วทั้งระบบ ดังนั้นฟีเจอร์อย่าง tile shaders ที่ต้องมีการรองรับพิเศษมากทั่วทั้งตัวเรนเดอร์ และใช้งานได้เฉพาะบนฮาร์ดแวร์รุ่นใหม่เท่านั้น จึงน่าสนใจน้อยกว่า

อย่างไรก็ตาม เราใช้เวลาบางส่วนในการปรับแต่งส่วนต่างๆ ของระบบหลังบ้านเพื่อให้ทำงานได้ *เร็วขึ้น* - โดยใช้การอัปโหลดเท็กซ์เจอร์แบบอะซิงโครนัสอย่างสมบูรณ์เพื่อลดการกระตุกระหว่างการโหลดระดับ ซึ่งไม่มีปัญหาใดๆ เลย การปรับแต่งหน่วยความจำที่กล่าวถึงก่อนหน้านี้บน macOS การปรับแต่งการกระจายงานของ CPU ในหลายๆ จุดของระบบหลังบ้านโดยการลดการพลาดแคช เป็นต้น และ - หนึ่งในคุณสมบัติใหม่ไม่กี่อย่างที่เราได้รับการสนับสนุนอย่างชัดเจน - การใช้การจัดเก็บพื้นผิวแบบไม่ใช้หน่วยความจำเมื่อมีให้ เพื่อลดหน่วยความจำที่จำเป็นสำหรับระบบเงาใหม่ของเราอย่างมีนัยสำคัญ

อนาคต

โดยรวมแล้ว การที่เราไม่ต้องใช้เวลามากเกินไปกับการปรับปรุง Metal นั้นถือเป็นเรื่องดี - โค้ดที่เขียนไว้เมื่อ 3 ปีที่แล้ว โดยรวมแล้วทำงานได้ดี รวดเร็ว และเสถียร ซึ่งเป็นสัญญาณที่ดีของ API ที่มีความสมบูรณ์ การพอร์ตไปยัง Metal เป็นการลงทุนที่คุ้มค่า เมื่อพิจารณาจากเวลาที่ใช้และประโยชน์อย่างต่อเนื่องที่เรามอบให้กับเราและผู้ใช้ของเรา

เราประเมินความสมดุลระหว่างปริมาณงานที่เราทำสำหรับ API ต่าง ๆ อยู่เสมอ - มีความเป็นไปได้สูงที่เราจะต้องเจาะลึกในส่วนที่ทันสมัยมากขึ้นของ Metal API สำหรับโครงการเรนเดอร์ในอนาคตบางโครงการ หากเกิดขึ้นจริง เราจะแน่ใจว่าจะเขียนโพสต์เพิ่มเติมเกี่ยวกับเรื่องนี้!

  1. ใช่ครับ/ค่ะ แล้วก็อาจจะใช้เวลาสักสัปดาห์ในการแก้ไขบั๊กที่พบระหว่างการทดสอบ
  2. ตัวเลขเหล่านี้เป็นข้อมูลที่อัปเดตต่อเฟรม 128 KB (สองพื้นที่ 32x16x32 RGBA8) บน A10

ทั้งบริษัท Roblox Corporation และบล็อกนี้ไม่ได้รับรองหรือสนับสนุนบริษัทหรือบริการใด ๆ ทั้งสิ้น นอกจากนี้ ไม่มีการรับประกันหรือคำมั่นสัญญาใด ๆ เกี่ยวกับความถูกต้อง ความน่าเชื่อถือ หรือความสมบูรณ์ของข้อมูลที่ปรากฏในบล็อกนี้

บทความบล็อกนี้ได้รับการเผยแพร่ครั้งแรกบนบล็อก Roblox Tech