จาก WebGL สู่ WebGPU

François Beaufort
François Beaufort

ในฐานะนักพัฒนา WebGL คุณอาจทั้งตื่นเต้นและกังวลใจที่จะเริ่มต้นใช้งาน WebGPU ซึ่งเป็น API กราฟิกสมัยใหม่ที่นำมาใช้ในเว็บเพื่อแทนที่ WebGL

คุณก็คงโล่งใจเมื่อทราบว่า WebGL และ WebGPU มีแนวคิดหลักหลายอย่างที่เหมือนกัน ทั้ง 2 API ช่วยให้คุณสามารถเรียกใช้โปรแกรมขนาดเล็กที่เรียกว่า Shader ใน GPU ได้ WebGL รองรับเวิร์กเท็กซ์และแฟรกเมนต์ Shader ส่วน WebGPU รองรับ Compute Shader ด้วย WebGL ใช้ภาษาการจัดแสง OpenGL (GLSL) ส่วน WebGPU ใช้ภาษาการจัดแสง WebGPU (WGSL) แม้ว่าภาษาทั้ง 2 ภาษาจะแตกต่างกัน แต่แนวคิดพื้นฐานส่วนใหญ่จะเหมือนกัน

บทความนี้จะเน้นความแตกต่างบางประการระหว่าง WebGL กับ WebGPU เพื่อช่วยให้คุณเริ่มต้นใช้งาน

สถานะส่วนกลาง

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

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

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

หยุดซิงค์

ใน GPU การส่งคําสั่งและรอให้คําสั่งดำเนินการพร้อมกันนั้นมักจะไม่มีประสิทธิภาพ เนื่องจากอาจล้างไปป์ไลน์และทำให้เกิดฟองอากาศ โดยเฉพาะอย่างยิ่งใน WebGPU และ WebGL ซึ่งใช้สถาปัตยกรรมแบบหลายกระบวนการที่มีไดรเวอร์ GPU ที่ทำงานในกระบวนการแยกต่างหากจาก JavaScript

ตัวอย่างเช่น ใน WebGL การเรียก gl.getError() ต้องใช้ IPC แบบซิงค์จากกระบวนการ JavaScript ไปยังกระบวนการ GPU และกลับ ซึ่งอาจทำให้เกิดบั๊กในฝั่ง CPU เมื่อ 2 กระบวนการสื่อสารกัน

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

โปรแกรมประมวลผลเฉดสี

เชดเดอร์การประมวลผลคือโปรแกรมที่ทำงานบน GPU เพื่อทำการประมวลผลทั่วไป โดยจะใช้ได้เฉพาะใน WebGPU เท่านั้น ไม่ใช่ WebGL

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

การประมวลผลเฟรมวิดีโอ

การประมวลผลเฟรมวิดีโอโดยใช้ JavaScript และ WebAssembly มีข้อเสียอยู่บ้าง เช่น ต้นทุนในการคัดลอกข้อมูลจากหน่วยความจำ GPU ไปยังหน่วยความจำ CPU และการทำงานแบบขนานที่จำกัดซึ่งทำได้ด้วยเวิร์กเกอร์และเธรด CPU WebGPU ไม่มีการจํากัดเหล่านี้ จึงเหมาะสําหรับการประมวลผลเฟรมวิดีโอเนื่องจากการผสานรวมอย่างใกล้ชิดกับ WebCodecs API

ข้อมูลโค้ดต่อไปนี้แสดงวิธีนําเข้า VideoFrame เป็นพื้นผิวภายนอกใน WebGPU และประมวลผล คุณสามารถลองใช้เดโมนี้ได้

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

การย้ายแอปพลิเคชันได้โดยค่าเริ่มต้น

WebGPU จะบังคับให้คุณขอ limits โดยค่าเริ่มต้น requestDevice() จะแสดงผล GPUDevice ที่อาจไม่ตรงกับความสามารถของฮาร์ดแวร์ของอุปกรณ์จริง แต่แสดงผล GPU ทั้งหมดที่สมเหตุสมผลและมีค่าส่วนร่วมต่ำสุด การกําหนดให้นักพัฒนาแอปต้องขอขีดจํากัดของอุปกรณ์จะช่วยให้ WebGPU มั่นใจได้ว่าแอปพลิเคชันจะทํางานบนอุปกรณ์จํานวนมากที่สุด

การจัดการภาพพิมพ์แคนวาส

WebGL จะจัดการผืนผ้าใบโดยอัตโนมัติหลังจากที่คุณสร้างบริบท WebGL และระบุแอตทริบิวต์บริบท เช่น alpha, antialias, colorSpace, depth, preserveDrawingBuffer หรือ stencil

ส่วน WebGPU กำหนดให้คุณจัดการ Canvas ด้วยตนเอง ตัวอย่างเช่น หากต้องการใช้การลบรอยหยักใน WebGPU คุณจะต้องสร้างพื้นผิวแบบ Multisample และแสดงผลกับพื้นผิวนั้น จากนั้นคุณจะต้องเปลี่ยนพื้นผิวแบบ Multisample เป็นพื้นผิวปกติและวาดพื้นผิวนั้นลงในผืนผ้าใบ การจัดการด้วยตนเองนี้ช่วยให้คุณส่งออกไปยังผืนผ้าใบได้เท่าที่ต้องการจากออบเจ็กต์ GPUDevice รายการเดียว ในทางตรงกันข้าม WebGL จะสร้างบริบทได้เพียง 1 รายการต่อผืนผ้าใบเท่านั้น

ดูการสาธิต Canvas หลายรายการของ WebGPU

โปรดทราบว่าขณะนี้เบราว์เซอร์มีการจำกัดจำนวนภาพพิมพ์แคนวาส WebGL ต่อหน้า ขณะเขียนบทความนี้ Chrome และ Safari ใช้แคนวาส WebGL ได้พร้อมกันสูงสุด 16 รายการเท่านั้น ส่วน Firefox สร้างได้สูงสุด 200 รายการ ในทางกลับกัน ไม่มีการจํากัดจํานวนภาพพิมพ์แคนวาส WebGPU ต่อหน้า

ภาพหน้าจอแสดงจำนวนแคนวาส WebGL สูงสุดในเบราว์เซอร์ Safari, Chrome และ Firefox
จำนวนแคนวาส WebGL สูงสุดใน Safari, Chrome และ Firefox (จากซ้ายไปขวา) - demo

ข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์

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

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

นอกจากนี้ WebGPU ยังให้คุณระบุ label ที่กําหนดเองสําหรับออบเจ็กต์ WebGPU แต่ละรายการได้ด้วย จากนั้นเบราว์เซอร์จะใช้ป้ายกำกับนี้ในข้อความ GPUError คำเตือนในคอนโซล และเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ของเบราว์เซอร์

จากชื่อไปยังดัชนี

ใน WebGL สิ่งต่างๆ เชื่อมต่อกันด้วยชื่อ เช่น คุณสามารถประกาศตัวแปรแบบคงที่ชื่อ myUniform ใน GLSL และรับตำแหน่งโดยใช้ gl.getUniformLocation(program, 'myUniform') ซึ่งจะมีประโยชน์เมื่อคุณได้รับข้อผิดพลาดเนื่องจากพิมพ์ชื่อตัวแปรแบบคงที่ผิด

ในทางกลับกัน ใน WebGPU ทุกอย่างจะเชื่อมต่อกันทั้งหมดด้วยออฟเซตหรือดัชนีไบต์ (มักเรียกว่าตำแหน่ง) คุณมีหน้าที่รับผิดชอบในการดูแลให้ตำแหน่งของโค้ดใน WGSL และ JavaScript ซิงค์กันอยู่เสมอ

การสร้าง Mipmap

ใน WebGL คุณสามารถสร้าง MIP ระดับ 0 ของพื้นผิว แล้วเรียกใช้ gl.generateMipmap() จากนั้น WebGL จะสร้าง MIP ระดับอื่นๆ ทั้งหมดให้คุณ

ใน WebGPU คุณต้องสร้างมิpmap ด้วยตัวเอง ยังไม่มีฟังก์ชันในตัวสำหรับดำเนินการนี้ ดูข้อมูลเพิ่มเติมเกี่ยวกับผลการตัดสินได้ในการสนทนาเกี่ยวกับข้อกำหนด คุณสามารถใช้ไลบรารีที่มีประโยชน์ เช่น webgpu-utils เพื่อสร้างมิpmap หรือดูวิธีทําด้วยตนเองก็ได้

บัฟเฟอร์พื้นที่เก็บข้อมูลและพื้นผิวพื้นที่เก็บข้อมูล

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

  • ข้อมูลบัฟเฟอร์พื้นที่เก็บข้อมูลที่ส่งไปยังโปรแกรมเปลี่ยนรูปแบบสามารถมีขนาดใหญ่กว่าบัฟเฟอร์แบบคงที่ได้ แม้ว่าข้อกำหนดจะระบุว่าการเชื่อมโยงบัฟเฟอร์แบบสอดคล้องกันมีขนาดได้สูงสุด 64 KB (ดู maxUniformBufferBindingSize) แต่ขนาดสูงสุดของการเชื่อมโยงบัฟเฟอร์พื้นที่เก็บข้อมูลใน WebGPU จะต้องไม่น้อยกว่า 128 MB (ดู maxStorageBufferBindingSize)

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

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

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

การเปลี่ยนแปลงบัฟเฟอร์และพื้นผิว

ใน WebGL คุณสามารถสร้างบัฟเฟอร์หรือพื้นผิว แล้วเปลี่ยนขนาดได้ทุกเมื่อด้วย gl.bufferData() และ gl.texImage2D() ตามลำดับ

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

ความแตกต่างของรูปแบบพื้นที่ทำงาน

ใน WebGL ช่วง clip space ของ Z จะอยู่ที่ -1 ถึง 1 ใน WebGPU ช่วงพื้นที่คลิป Z จะอยู่ที่ 0 ถึง 1 ซึ่งหมายความว่าวัตถุที่มีค่า z เท่ากับ 0 จะอยู่ใกล้กับกล้องมากที่สุด ส่วนวัตถุที่มีค่า z เท่ากับ 1 จะอยู่ไกลที่สุด

ภาพช่วงพื้นที่คลิป Z ใน WebGL และ WebGPU
ช่วงพื้นที่คลิป Z ใน WebGL และ WebGPU

WebGL ใช้รูปแบบ OpenGL ซึ่งแกน Y จะขึ้นและแกน Z จะหันเข้าหาผู้ชม WebGPU ใช้รูปแบบ Metal ซึ่งแกน Y จะลงและแกน Z จะอยู่นอกหน้าจอ โปรดทราบว่าทิศทางของแกน Y จะลงต่ำในพิกัดเฟรมบ buffer, พิกัดวิวพอร์ต และพิกัดเศษ/พิกเซล ในมุมมองคลิป ทิศทางแกน Y จะยังคงขึ้นอยู่เช่นเดียวกับใน WebGL

ขอขอบคุณ

ขอขอบคุณ Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell และ Rachel Andrew ที่ตรวจสอบบทความนี้

นอกจากนี้ เราขอแนะนําให้ไปที่ WebGPUFundamentals.org เพื่อดูรายละเอียดความแตกต่างระหว่าง WebGPU กับ WebGL