เจาะลึกการแสดงภาพNG: BlinkNG

สเตฟาน ซาเกอร์
Stefan Zager
คริส แฮร์เรลสัน
Chris Harrelson

Blink หมายถึงการใช้งานแพลตฟอร์มเว็บของ Chromium ซึ่งครอบคลุมทุกระยะการแสดงผลก่อนทำการประกอบ ซึ่งจะกลายเป็นการคอมมิตคอมโพสิเตอร์ อ่านข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรมการแสดงผลกะพริบในบทความก่อนหน้าในซีรีส์นี้

Blink เริ่มต้นจากการเป็น Fork ของ WebKit ซึ่งเป็นการแยกของ KHTML ตั้งแต่ปี 1998 โดยมีโค้ดที่เก่าที่สุด (และสำคัญที่สุด) บางส่วนใน Chromium และภายในปี 2014 ก็มีการแสดงให้เห็นอายุของโค้ดนี้แล้ว ในปีนั้น เราเริ่มต้นโครงการที่ท้าทายอันยิ่งใหญ่ภายใต้แบนเนอร์ของสิ่งที่เราเรียกว่า BlinkNG โดยมีเป้าหมายที่จะแก้ไขปัญหาความบกพร่องในระบบองค์กรและโครงสร้างของ Blink Code ที่มีมาอย่างยาวนาน บทความนี้จะอธิบายเกี่ยวกับ BlinkNG และโปรเจ็กต์ส่วนประกอบต่างๆ ทั้งเหตุผลที่เราทำ สิ่งที่บริษัททำสำเร็จ หลักการชี้นำที่ช่วยปรับปรุงการออกแบบ และโอกาสสำหรับการปรับปรุงในอนาคต

ไปป์ไลน์การแสดงภาพก่อนและหลัง BlinkNG

กำลังแสดงผลก่อน NG

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

  • จำเป็นต้องอัปเดตเอาต์พุตของรูปแบบ เค้าโครง หรือสีหรือไม่
  • ข้อมูลเหล่านี้จะได้รับค่า "สุดท้าย" เมื่อใด
  • คุณควรแก้ไขข้อมูลเหล่านี้เมื่อใด
  • ออบเจ็กต์นี้จะถูกลบเมื่อใด

ซึ่งตัวอย่างมีดังนี้

รูปแบบจะสร้าง ComputedStyle ตามสไตล์ชีต แต่ ComputedStyle จะเปลี่ยนแปลงไม่ได้ ในบางกรณีอาจมีการแก้ไขในไปป์ไลน์ต่อๆ ไป

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

รูปแบบจะสร้างโครงสร้างข้อมูลอุปกรณ์เสริมที่กำหนดเส้นทางของการประกอบภาพ และโครงสร้างข้อมูลเหล่านั้นได้รับการแก้ไขในทุกๆ ระยะหลัง style

ในระดับที่ต่ำกว่า ประเภทข้อมูลการแสดงผลส่วนใหญ่ประกอบด้วยต้นไม้ที่จำเพาะเจาะจง (เช่น DOM Tree, แผนผังรูปแบบ, แผนผังเลย์เอาต์, แผนผังพร็อพเพอร์ตี้สำหรับการแสดงผล) และระยะการแสดงผลจะใช้งานเป็นการเดินต้นไม้แบบซ้ำๆ ตามหลักการแล้ว การเดินบนต้นไม้ควรมี กล่าวคือ เมื่อประมวลผลโหนดต้นไม้ที่ระบุ เราไม่ควรเข้าถึงข้อมูลใดๆ นอกโครงสร้างย่อยที่รากอยู่ที่โหนดนั้น ซึ่งไม่เป็นความจริงก่อนการแสดงผล NG ต้นไม้จะเดินข้อมูลที่มีการเข้าถึงบ่อยครั้งจากระดับบนของโหนดที่ประมวลผลอยู่ ซึ่งทำให้ระบบมีความเปราะบางและเกิดข้อผิดพลาดได้ง่าย และการที่ต้นไม้เดินจากที่ไหนก็ได้นอกจากรากของต้นไม้นั้น เป็นไปไม่ได้เลย

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

สิ่งที่เราเปลี่ยนแปลง

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

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

รายการของโปรเจ็กต์ย่อยของ BlinkNG ทั้งหมดอาจเป็นเรื่องน่าเบื่อหน่าย แต่ต่อไปนี้เป็นผลที่ตามมาบางส่วน

วงจรเอกสาร

คลาส DocumentLifecycle จะติดตามความคืบหน้าของเราผ่านไปป์ไลน์การแสดงผล ซึ่งช่วยให้เราดำเนินการตรวจสอบพื้นฐานที่บังคับใช้ค่าตัวแปรที่แสดงอยู่ก่อนหน้านี้ได้ เช่น

  • หากเรากำลังแก้ไขพร็อพเพอร์ตี้ ComputedStyle วงจรเอกสารต้องเป็น kInStyleRecalc
  • หากสถานะ DocumentLifecycle เป็น kStyleClean หรือใหม่กว่า NeedsStyleRecalc() จะต้องแสดงผล false สำหรับโหนดใดก็ตามที่แนบมา
  • เมื่อเข้าสู่ช่วงวงจร paint สถานะของวงจรต้องเป็น kPrePaintClean

ในระหว่างที่นำ BlinkNG มาใช้ เราได้กำจัดเส้นทางโค้ดที่ละเมิดค่าที่ไม่เปลี่ยนแปลงเหล่านี้อย่างเป็นระบบ รวมถึงเพิ่มการยืนยันอื่นๆ อีกมากตลอดทั้งโค้ดเพื่อให้แน่ใจว่าเราจะไม่ถดถอย

หากคุณเคยเอาแต่เฝ้าดูโค้ดการแสดงผลระดับต่ำ คุณอาจถามตัวเองว่า "ฉันมาที่นี่ได้อย่างไร" ดังที่กล่าวไว้ก่อนหน้านี้ มีประเด็นมากมายในการเข้าสู่ไปป์ไลน์การแสดงภาพ ซึ่งก่อนหน้านี้จะประกอบด้วยเส้นทางการโทรที่เกิดซ้ำและกลับมาใช้ซ้ำ รวมถึงจุดที่เราเข้าสู่ไปป์ไลน์ในช่วงกลางแทนที่จะเริ่มต้นตั้งแต่ต้น ในการใช้งาน BlinkNG เราวิเคราะห์เส้นทางการโทรเหล่านี้และพิจารณาว่าเส้นทางการโทรทั้งหมดสามารถปรับได้ใน 2 สถานการณ์พื้นฐาน ดังนี้

  • ข้อมูลการแสดงผลทั้งหมดจำเป็นต้องได้รับการอัปเดต เช่น เมื่อสร้างพิกเซลใหม่สำหรับโฆษณาแบบดิสเพลย์หรือทำการทดสอบ Hit สำหรับการกำหนดเป้าหมายเหตุการณ์
  • เราจำเป็นต้องใช้ค่าที่เป็นปัจจุบันสำหรับคำค้นหาที่เจาะจงซึ่งตอบได้โดยไม่ต้องอัปเดตข้อมูลการแสดงผลทั้งหมด ซึ่งรวมถึงการค้นหา JavaScript ส่วนใหญ่ เช่น node.offsetTop

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

รูปแบบการเดินท่อ เลย์เอาต์ และการทำสีล่วงหน้า

ระยะการแสดงผลก่อน paint จะส่งผลให้เกิดสิ่งต่อไปนี้

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

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

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

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

ต่อไปนี้เป็นโครงการสำคัญที่ขจัดความบกพร่องทางสถาปัตยกรรมของระยะการแสดงผลก่อนการทาสี

Project Squad: ไปป์ไลน์สไตล์

โครงการนี้สามารถรับมือกับจุดบกพร่อง 2 อย่างหลักๆ ได้ในขั้นตอนของสไตล์ซึ่งทำให้ระบบไม่สามารถเจาะระบบได้อย่างถูกต้อง ดังนี้

เอาต์พุตหลักของเฟสรูปแบบมี 2 เอาต์พุต ได้แก่ ComputedStyle ที่มีผลลัพธ์ของการเรียกใช้อัลกอริทึมการเรียงซ้อน CSS บนแผนผัง DOM และต้นไม้ LayoutObjects ซึ่งกำหนดลำดับการดำเนินการสำหรับระยะเลย์เอาต์ ตามหลักแล้ว การเรียกใช้อัลกอริทึมการเรียงซ้อนควรเกิดขึ้นก่อนที่จะสร้างแผนผังเลย์เอาต์ แต่ก่อนหน้านี้การดำเนินการทั้ง 2 อย่างนี้แทรกเข้ามา Project Squad ประสบความสำเร็จในการแยก 2 ระยะนี้ออกเป็นระยะต่างๆ ที่แยกจากกัน

ก่อนหน้านี้ ComputedStyle ไม่ได้รับค่าสุดท้ายเสมอไปในระหว่างการคำนวณสไตล์ใหม่ มีบางสถานการณ์ที่ ComputedStyle ได้รับการอัปเดตในขั้นตอนไปป์ไลน์ภายหลัง Project Squad เปลี่ยนโครงสร้างภายในเส้นทางโค้ดเหล่านี้ใหม่ เพื่อไม่ให้ ComputedStyle แก้ไขหลังจากระยะรูปแบบ

LayoutNG: ไปป์ไลน์เลย์เอาต์

โปรเจ็กต์สำคัญนี้ ซึ่งเป็นหนึ่งในรากฐานสำคัญของ RenderingNG และเป็นการปรับโฉมขั้นตอนการแสดงผลเลย์เอาต์ใหม่ทั้งหมด เราจะไม่ดำเนินการกับทั้งโปรเจ็กต์ในที่นี้ แต่มีแง่มุมที่โดดเด่น 2-3 อย่างสำหรับโปรเจ็กต์ BlinkNG โดยรวม ได้แก่

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

ช่วงก่อนลงสี

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

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

ต้นไม้สมบัติ: เรขาคณิตที่สม่ำเสมอ

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

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

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

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

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

วัสดุเชิงประกอบหลังการพ่นสี: สีท่อและการประสาน

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

กรณีนี้ทำให้เกิดปัญหาสำคัญ เนื่องจากจำเป็นต้องมีทรัพยากร Dependency แบบวงกลมในโค้ดมากขึ้นหรือน้อยลง ซึ่งเป็นปัญหาใหญ่สำหรับไปป์ไลน์การแสดงผล มาดูตัวอย่างกัน สมมติว่าเราต้องinvalidate (หมายความว่าเราต้องวาดรายการที่แสดงใหม่และทำการแรสเตอร์อีกครั้ง) ความจำเป็นในการทำให้ไม่ถูกต้องอาจมาจากการเปลี่ยนแปลงใน DOM หรือจากรูปแบบหรือเลย์เอาต์ที่เปลี่ยนไป แต่แน่นอนว่าเราต้องการทำให้ส่วนที่มีการเปลี่ยนแปลงจริงๆ นั้นเป็นโมฆะเท่านั้น ซึ่งหมายถึงการค้นหาเลเยอร์ที่ประกอบขึ้นที่ได้รับผลกระทบ แล้วทำให้รายการการแสดงผลบางส่วนหรือทั้งหมดของเลเยอร์เหล่านั้นเป็นโมฆะ

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

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

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

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

ประโยชน์

ดังที่คุณได้เห็นแล้ว ไปป์ไลน์การแสดงผลที่กำหนดไว้เป็นอย่างดีจะให้ประโยชน์อย่างมากในระยะยาว มีข้อมูลมากกว่าที่คุณคิด:

  • ปรับปรุงความน่าเชื่อถือได้อย่างมาก: เรื่องนี้ค่อนข้างตรงไปตรงมา โค้ดที่สะอาดตาขึ้นซึ่งมีอินเทอร์เฟซที่กำหนดชัดเจนและเข้าใจง่ายจะทำให้เข้าใจ เขียน และทดสอบได้ง่ายขึ้น ซึ่งจะทำให้เชื่อถือได้มากขึ้น นอกจากนี้ยังทำให้โค้ดปลอดภัยและเสถียรยิ่งขึ้น โดยมีข้อขัดข้องและข้อบกพร่องในการใช้งานที่น้อยลงหลังการใช้งานน้อยลง
  • การขยายความครอบคลุมของการทดสอบ: เราได้เพิ่มการทดสอบใหม่ๆ มากมายให้กับชุดโปรแกรมของเราระหว่าง BlinkNG ซึ่งประกอบด้วยการทดสอบหน่วยที่ให้การยืนยันความถูกต้องที่มุ่งเน้นเรื่องภายใน, การทดสอบการถดถอยที่ป้องกันไม่ให้เรานำข้อบกพร่องเก่าๆ ที่เราได้แก้ไขไปแล้ว (จำนวนมาก) กลับมาอีกครั้ง และส่วนเสริมมากมายที่ออกสู่สาธารณะเกี่ยวกับชุดทดสอบแพลตฟอร์มเว็บที่ได้รับการบำรุงรักษารวมกัน ซึ่งเบราว์เซอร์ทุกชนิดใช้เพื่อวัดการปฏิบัติตามมาตรฐานเว็บ
  • ขยายได้ง่ายขึ้น: หากระบบแบ่งย่อยออกเป็นองค์ประกอบต่างๆ ที่ชัดเจน ก็ไม่จำเป็นต้องเข้าใจองค์ประกอบอื่นๆ ในรายละเอียดทุกระดับเพื่อดำเนินการต่อกับองค์ประกอบปัจจุบัน ซึ่งช่วยให้ทุกคนเพิ่มมูลค่าให้กับโค้ดการแสดงผลได้ง่ายขึ้นโดยไม่ต้องเป็นผู้เชี่ยวชาญเชิงลึก ตลอดจนช่วยให้เหตุผลเกี่ยวกับลักษณะการทำงานของระบบทั้งหมดได้ง่ายขึ้น
  • ประสิทธิภาพ: การเพิ่มประสิทธิภาพอัลกอริทึมที่เขียนด้วยโค้ดสปาเก็ตตี้นั้นยากพอสมควร แต่แทบจะเป็นไปไม่ได้เลยที่จะบรรลุสิ่งที่ยิ่งใหญ่กว่านั้น เช่น การเลื่อนแบบชุดข้อความสากลและภาพเคลื่อนไหว หรือกระบวนการและชุดข้อความสำหรับการแยกเว็บไซต์โดยไม่มีไปป์ไลน์ดังกล่าว การทำงานพร้อมกันอาจช่วยให้เราปรับปรุงประสิทธิภาพได้อย่างมาก แต่ก็เป็นเรื่องที่ซับซ้อนด้วยเช่นกัน
  • ความสามารถในการควบคุมและการยับยั้ง: BlinkNG มีฟีเจอร์ใหม่ๆ หลายอย่างเกิดขึ้นได้ซึ่งใช้กระบวนการทำงานในแบบใหม่ๆ ที่แปลกใหม่ เช่น ในกรณีที่เราต้องการเรียกใช้เพียงไปป์ไลน์การแสดงผลจนกว่างบประมาณจะหมดอายุ หรือข้ามการแสดงผลสำหรับต้นไม้ย่อยที่ทราบว่าไม่เกี่ยวข้องกับผู้ใช้ในขณะนี้ใช่ไหม ซึ่งเป็นสิ่งที่คุณสมบัติ CSS ระดับการเข้าถึงเนื้อหา เปิดใช้ได้ จะทำอย่างไรให้สไตล์ของคอมโพเนนต์ขึ้นอยู่กับเลย์เอาต์ของคอมโพเนนต์นั้นๆ นั่นคือการค้นหาคอนเทนเนอร์

กรณีศึกษา: การค้นหาคอนเทนเนอร์

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

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

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

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

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

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

อนาคต: การเรียบเรียงเทรดนอกหลัก ... และอีกมากมาย!

ไปป์ไลน์การแสดงผลที่แสดงที่นี่ ทำงานเร็วกว่าการติดตั้งใช้งาน RenderingNG ปัจจุบันอยู่เล็กน้อย โดยจะแสดงการสร้างเลเยอร์ให้อยู่นอกเทรดหลัก แต่ปัจจุบันยังคงอยู่ในเทรดหลัก อย่างไรก็ตาม อีกไม่นานกระบวนการดังกล่าวก็เสร็จสิ้น เนื่องจากตอนนี้เราจัดส่ง Composite After Paint แล้ว และการทำเลเยอร์เกิดขึ้นหลังจากการลงสี

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

ข่าวดีก็คือคุณไม่ต้องเป็นแบบนี้ สถาปัตยกรรมของ Chromium ในลักษณะนี้มีมาตั้งแต่สมัย KHTML ซึ่งการเรียกใช้แบบเทรดเดียวเป็นโมเดลหลักในการจัดโปรแกรม ในยุคที่โปรเซสเซอร์หลายแกนกลายเป็นอุปกรณ์ทั่วไปในอุปกรณ์ระดับผู้บริโภค สมมติฐานแบบเทรดเดียวก็กลายเป็น Blink ไปทั่วแล้ว (ก่อนหน้านี้คือ WebKit) เราต้องการเพิ่มการแยกชุดข้อความย่อยเข้าไปในเครื่องมือแสดงผลมาเป็นเวลานานแล้ว แต่เป็นสิ่งที่เป็นไปไม่ได้ในระบบเก่า จุดประสงค์หลักอย่างหนึ่งของการแสดงภาพ NG คือการขุดตัวเองจากหลุมนี้ และทำให้สามารถย้ายงานการแสดงภาพบางส่วนหรือทั้งหมดไปยังเทรดอื่น (หรือเทรด)

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

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

การรอตรงปีกคือการผสมเทรดหลัก โดยมีเป้าหมายเพื่อทำให้เครื่องมือการแสดงผลตรงกับภาพโดยย้ายการสร้างเลเยอร์ออกจากเทรดหลักและเทรดของผู้ปฏิบัติงาน การดำเนินการนี้จะลดความคับคั่งในเทรดหลักโดยการลดภาระงานการแสดงผล เช่นเดียวกับคอมมิตแบบไม่บล็อก โครงการเช่นนี้คงเป็นไปไม่ได้หากไม่มีการปรับปรุงสถาปัตยกรรมของ Composite After Paint

และยังมีโปรเจ็กต์อื่นๆ รอเราอยู่อีก (ลองเล่นไหม) ในที่สุดเราก็มีรากฐานที่ทำให้สามารถทดสอบการกระจายการแสดงผลซ้ำ และเราก็ตื่นเต้นมากที่จะได้เห็นสิ่งที่เป็นไปได้