จาก WebGL สู่ WebGPU

François Beaufort
François Beaufort

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

การทราบว่า WebGL และ WebGPU มีแนวคิดหลักหลายอย่างร่วมกันช่วยให้มั่นใจได้ ทั้ง 2 API ช่วยให้คุณเรียกใช้โปรแกรมขนาดเล็กที่เรียกว่า Shader ใน GPU ได้ WebGL รองรับ Vertex Shader และ Fragment Shader ในขณะที่ WebGPU รองรับ Compute Shader ด้วย WebGL ใช้ OpenGL Shading Language (GLSL) ส่วน WebGPU ใช้ WebGPU Shading Language (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 ได้รับการออกแบบมาให้ทำงานแบบอะซิงโครนัสอย่างสมบูรณ์เพื่อหลีกเลี่ยงฟองสบู่เหล่านี้ รูปแบบข้อผิดพลาดและการดำเนินการอื่นๆ ทั้งหมดจะเกิดขึ้นแบบไม่พร้อมกัน เช่น เมื่อคุณสร้างเท็กซ์เจอร์ การดำเนินการจะดูเหมือนสำเร็จทันที แม้ว่าเท็กซ์เจอร์นั้นจะเป็นข้อผิดพลาดก็ตาม คุณจะค้นพบข้อผิดพลาดได้แบบไม่พร้อมกันเท่านั้น การออกแบบนี้ช่วยให้การสื่อสารข้ามกระบวนการเป็นไปอย่างราบรื่นและช่วยให้แอปพลิเคชันมีประสิทธิภาพที่เชื่อถือได้

Compute Shader

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

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

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

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

ข้อมูลโค้ดต่อไปนี้แสดงวิธีนำเข้า 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 มั่นใจได้ว่าแอปพลิเคชันจะทำงานบนอุปกรณ์ได้มากที่สุด

การจัดการ Canvas

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

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

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

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

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

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

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 คุณต้องสร้าง MIPmap ด้วยตนเอง ไม่มีฟังก์ชันในตัวที่ทำเช่นนี้ได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับการตัดสินใจได้ที่การสนทนาเกี่ยวกับข้อกำหนด คุณสามารถใช้ไลบรารีที่มีประโยชน์ เช่น webgpu-utils เพื่อสร้าง MIPmap หรือดูวิธีสร้างด้วยตนเอง

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

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

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

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

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

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

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

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

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

ความแตกต่างของแบบแผนการเว้นวรรค

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

การรับทราบ

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

นอกจากนี้ เราขอแนะนำ WebGPUFundamentals.org เพื่อให้คุณได้เจาะลึกถึงความแตกต่างระหว่าง WebGPU กับ WebGL