การแสดงผลแบบเร่งใน Chrome

โมเดลเลเยอร์

Tom Wiltzius
Tom Wiltzius

บทนำ

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

ข้อควรระวังเรื่องไขมันสูง

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

นอกจากนี้ โปรดทราบว่าบทความนี้ทั้งหมดพูดถึงสถาปัตยกรรมการแสดงผลหลักของ Chrome ที่เปลี่ยนแปลงไปอย่างรวดเร็ว บทความนี้พยายามครอบคลุมเฉพาะสิ่งที่ไม่น่าจะเปลี่ยนแปลง แต่ไม่ได้รับประกันว่าข้อมูลทั้งหมดจะยังคงมีผลบังคับใช้ในอีก 6 เดือน

คุณควรเข้าใจว่า Chrome มีเส้นทางการแสดงผล 2 แบบมาระยะหนึ่งแล้ว ได้แก่ เส้นทางที่เร่งการแสดงผลด้วยฮาร์ดแวร์และเส้นทางซอฟต์แวร์แบบเก่า ขณะที่เขียนหน้านี้ หน้าเว็บทั้งหมดจะมุ่งเน้นการเร่งฮาร์ดแวร์ใน Windows, ChromeOS และ Chrome สำหรับ Android สำหรับ Mac และ Linux เฉพาะหน้าเว็บที่ต้องใช้การประกอบเนื้อหาบางส่วนลงในเส้นทางที่เร่งรัดจนเกินไป (ดูข้อมูลเพิ่มเติมด้านล่างว่าต้องใส่องค์ประกอบใด) แต่อีกไม่นานทุกหน้าก็จะไปอยู่ในเส้นทางที่มีการเร่งความเร็วด้วยเช่นกัน

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

จาก DOM ไปยังหน้าจอ

ขอแนะนำเลเยอร์

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

จริงๆ แล้วใน Chrome จะมีเลเยอร์อยู่หลายประเภท ได้แก่ เครือข่าย RenderLayers ซึ่งเป็นเลเยอร์ย่อยของ DOM และ GraphicsParameters สิ่งหลังน่าสนใจที่สุดสำหรับเราที่นี่ เนื่องจาก GraphicsLayers เป็นพื้นผิวที่จะอัปโหลดไปยัง GPU ในที่นี้เราจะพูดว่า "เลเยอร์" ต่อไปหมายถึง GraphicsLayer

มองข้ามคำศัพท์เกี่ยวกับ GPU: พื้นผิวคืออะไร ให้คิดว่านี่เป็นรูปภาพบิตแมปที่ย้ายจากหน่วยความจำหลัก (เช่น RAM) ไปยังหน่วยความจำวิดีโอ (เช่น VRAM ใน GPU) เมื่อกราฟิกอยู่บน GPU คุณสามารถแมปกับเรขาคณิตแบบตาข่ายในวิดีโอเกมหรือโปรแกรม CAD เทคนิคนี้จะถูกนำไปใช้ในการสร้าง "สกิน" โมเดลโครงกระดูก 3 มิติ Chrome ใช้พื้นผิวเพื่อนำเนื้อหาของหน้าเว็บส่วนหนึ่งมาไว้บน GPU คุณสามารถจับคู่พื้นผิวกับตำแหน่งและการแปลงต่างๆ ได้โดยใช้ราคาถูกลง นี่คือวิธีการทำงานของ 3D CSS และยังเป็นวิธีที่ยอดเยี่ยมสำหรับการเลื่อนอย่างรวดเร็ว แต่จะมีมากกว่าในทั้ง 2 วิธีนี้ในภายหลัง

มาดูตัวอย่าง 2-3 ข้อเพื่ออธิบายแนวคิดเลเยอร์กัน

เครื่องมือที่มีประโยชน์มากเมื่อศึกษาเลเยอร์ใน Chrome คือธง "แสดงเส้นขอบของเลเยอร์แบบผสม" ในการตั้งค่า (เช่น ไอคอนฟันเฟืองขนาดเล็ก) ในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ ใต้ส่วนหัว "การแสดงผล" ซึ่งเพียงแค่ไฮไลต์ตำแหน่งต่างๆ บนหน้าจอ มาเปิดกัน ภาพหน้าจอและตัวอย่างเหล่านี้มาจาก Chrome Canary เวอร์ชันล่าสุด นั่นคือ Chrome 27 ในขณะที่เขียนบทความนี้

รูปที่ 1: หน้าเว็บเลเยอร์เดียว

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
ภาพหน้าจอของเลเยอร์แบบผสมที่ใช้แสดงผลรอบเลเยอร์ฐานของหน้า
ภาพหน้าจอของเลเยอร์แบบผสมที่ใช้แสดงผลรอบเลเยอร์ฐานของหน้า

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

รูปที่ 2: องค์ประกอบในเลเยอร์ของตัวเอง

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
ภาพหน้าจอของเส้นขอบแสดงผลของเลเยอร์ที่หมุน
ภาพหน้าจอของเส้นขอบแสดงผลของเลเยอร์ที่หมุน

การใส่พร็อพเพอร์ตี้ 3D CSS ใน <div> ที่หมุนตำแหน่งนั้นจะช่วยให้เราเห็นได้ว่าองค์ประกอบมีลักษณะอย่างไรเมื่อองค์ประกอบมีเลเยอร์ของตัวเอง ให้สังเกตเส้นขอบสีส้มซึ่งแสดงโครงร่างเลเยอร์ในมุมมองนี้

เกณฑ์การสร้างเลเยอร์

มีอะไรอีกบ้างที่ได้รับเลเยอร์ของตัวเอง การเรียนรู้ของ Chrome ที่นี่ได้เปลี่ยนแปลงไปตามเวลาและยังคงดำเนินต่อไป แต่ในปัจจุบันมีการสร้างเลเยอร์ทริกเกอร์ต่อไปนี้

  • การแปลงคุณสมบัติ CSS หรือมุมมอง 3 มิติ
  • องค์ประกอบ <video> รายการใช้การถอดรหัสวิดีโอแบบเร่ง
  • องค์ประกอบ <canvas> ที่มีบริบท 3 มิติ (WebGL) หรือบริบท 2 มิติแบบเร่ง
  • ปลั๊กอินแบบผสม (เช่น Flash)
  • องค์ประกอบที่มีภาพเคลื่อนไหว CSS สำหรับความทึบแสงหรือใช้การเปลี่ยนรูปแบบแบบเคลื่อนไหว
  • องค์ประกอบที่มีตัวกรอง CSS ที่มีการเร่งความเร็ว
  • องค์ประกอบมีองค์ประกอบสืบทอดที่มีเลเยอร์ประกอบ (กล่าวคือ หากองค์ประกอบมีองค์ประกอบย่อยที่อยู่ในเลเยอร์ของตนเอง)
  • องค์ประกอบมีข้างเคียงที่มีดัชนีลำดับ Z ต่ำกว่าซึ่งมีเลเยอร์ประกอบ (กล่าวคือ องค์ประกอบดังกล่าวแสดงผลที่ด้านบนของเลเยอร์แบบผสม)

ประโยชน์ที่นำไปปฏิบัติได้จริง: แอนิเมชัน

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

รูปที่ 3: เลเยอร์แบบเคลื่อนไหว

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div>I am a strange root.</div>
</body>
</html>

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

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

วันที่ ภาพหน้าจอแสดงไทม์ไลน์ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ขณะเคลื่อนไหว
ภาพหน้าจอแสดงไทม์ไลน์ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ขณะสร้างภาพเคลื่อนไหว

ไม่ถูกต้อง! การทำซ้ำ

แต่ถ้าเนื้อหาของเลเยอร์มีการเปลี่ยนแปลง จะต้องทาสีใหม่อีก

รูปที่ 4: การทำซ้ำเลเยอร์

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" value="repaint">
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

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

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

วันที่ ภาพหน้าจอของช่องทำเครื่องหมายแสดงช่องสี
ภาพหน้าจอของช่องทำเครื่องหมายแสดงช่องสี

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

วันที่ ภาพหน้าจอของไทม์ไลน์ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ที่ทาสีเลเยอร์ใหม่
ภาพหน้าจอของไทม์ไลน์ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ที่ทาสีเลเยอร์ใหม่

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

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

วิธีที่ดีที่สุดในการพิจารณาว่าปัญหานี้ส่งผลกระทบต่อสิ่งที่คุณกำลังดำเนินการอยู่หรือไม่ คือการใช้ไทม์ไลน์ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์และเครื่องมือแสดงภาพสี่เหลี่ยมผืนผ้า เพื่อดูว่าคุณจะทาสีใหม่เมื่อใดหรือไม่ จากนั้นลองระบุตำแหน่งที่คุณแก้ไข DOM ก่อนการส่งต่อ/ทาสีใหม่ หากการวาดภาพเป็นสิ่งที่หลีกเลี่ยงไม่ได้แต่ดูเหมือนจะใช้เวลานานจนไม่สมเหตุสมผล โปรดอ่านบทความของ Eberhard Gräther เกี่ยวกับโหมดการลงสีแบบต่อเนื่องในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์

นำมาประกอบกัน: DOM ไปยังหน้าจอ

แล้ว Chrome จะเปลี่ยน DOM เป็นรูปภาพหน้าจอได้อย่างไร โดยหลักการแล้ว:

  1. นำ DOM และแยกออกเป็นเลเยอร์ต่างๆ
  2. ทาสีแต่ละเลเยอร์เหล่านี้เป็นซอฟต์แวร์บิตแมปแยกกันอย่างอิสระ
  3. อัปโหลดไปยัง GPU เป็นพื้นผิว
  4. นำเลเยอร์ต่างๆ มาประกอบเข้าด้วยกันเป็นรูปภาพหน้าจอสุดท้าย

ซึ่งทั้งหมดจะต้องเกิดขึ้นในครั้งแรกที่ Chrome สร้างเฟรมของหน้าเว็บ แต่อาจต้องใช้ทางลัดบางอย่างสำหรับเฟรมในอนาคต ดังนี้

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

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

เท่านี้เองครับ คอยติดตามบทความเพิ่มเติมอีก 2-3 เรื่องเกี่ยวกับนัยยะในทางปฏิบัติของโมเดลเลเยอร์

แหล่งข้อมูลเพิ่มเติม