จาก WebGL สู่ WebGPU

François Beaufort
François Beaufort

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

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

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

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

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

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

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

หยุดซิงค์

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

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

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

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

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

ซึ่งแตกต่างจาก Vertex Shader และ Fragment 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() จะแสดงผล GPU ที่อาจไม่ตรงกับความสามารถของฮาร์ดแวร์ของอุปกรณ์จริง แต่จะแสดงผลตัวส่วนร่วมที่สมเหตุสมผลและต่ำสุดของ GPU ทั้งหมด การกําหนดให้นักพัฒนาแอปต้องขอขีดจํากัดของอุปกรณ์จะช่วยให้ WebGPU มั่นใจได้ว่าแอปพลิเคชันจะทํางานบนอุปกรณ์จํานวนมากที่สุด

การจัดการ Canvas

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 รายการ ในทางกลับกัน ไม่มีการจำกัดจำนวน Canvas ของ WebGPU ต่อหน้าเว็บ

ภาพหน้าจอแสดง Canvas ของ 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 คุณสามารถสร้างความละเอียดของพื้นผิวระดับ 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 จะใช้รูปแบบโลหะ โดยแกน Y อยู่ด้านล่างและแกน Z อยู่นอกหน้าจอ โปรดทราบว่าทิศทางแกน Y จะลดลงในพิกัดเฟรมบัฟเฟอร์ พิกัดวิวพอร์ต และพิกัดส่วนย่อย/พิกเซล ในคลิปเพลย์ ทิศทางแกน Y จะยังคงขึ้นอยู่เช่นเดียวกับใน WebGL

ขอขอบคุณ

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

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