การแสดงผลแบบเร่งใน 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 และเลเยอร์กราฟิก (GraphicsLayers) ซึ่งรับผิดชอบสำหรับซับต้นไม้ของเลเยอร์การแสดงผล ตัวเลือกหลังนี้น่าสนใจที่สุดสำหรับเรา เนื่องจาก GraphicsLayers คือสิ่งที่จะอัปโหลดไปยัง GPU เป็นพื้นผิว จากนี้ไปเราจะใช้คำว่า "เลเยอร์" เพื่อหมายถึง GraphicsLayer

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

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

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

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

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

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

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

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

การวางพร็อพเพอร์ตี้ CSS 3 มิติใน <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 อย่างจะกะพริบเป็นสีแดงเมื่อมีการคลิกปุ่ม

ภาพหน้าจอของช่องทำเครื่องหมาย &quot;แสดงสี่เหลี่ยมผืนผ้าสำหรับวาด&quot;
ภาพหน้าจอของช่องทําเครื่องหมาย "แสดงสี่เหลี่ยมผืนผ้าการวาด"

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

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

โปรดทราบว่า 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 และคอมโพสิตอีกครั้งเพื่อสร้างเฟรมใหม่ได้

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

เท่านี้ก่อน โปรดรอติดตามบทความอีก 2 บทความเกี่ยวกับผลกระทบที่ได้จากโมเดลเลเยอร์

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