สําหรับนักพัฒนาเว็บ WebGPU คือ API กราฟิกบนเว็บที่ให้การเข้าถึง GPU แบบรวมและรวดเร็ว WebGPU แสดงความสามารถของฮาร์ดแวร์สมัยใหม่และอนุญาตให้ทำการเรนเดอร์และการดำเนินการประมวลผลใน GPU ได้ คล้ายกับ Direct3D 12, Metal และ Vulkan
แม้ว่าจะเป็นความจริง แต่เรื่องราวนั้นยังไม่สมบูรณ์ WebGPU เกิดจากการร่วมแรงร่วมใจกัน ซึ่งรวมถึงบริษัทใหญ่ๆ อย่าง Apple, Google, Intel, Mozilla และ Microsoft ในจำนวนนี้ บางคนตระหนักว่า WebGPU อาจไม่ใช่แค่ JavaScript API แต่เป็น API กราฟิกข้ามแพลตฟอร์มสำหรับนักพัฒนาซอฟต์แวร์ในระบบนิเวศต่างๆ นอกเหนือจากเว็บ
เราได้เปิดตัว JavaScript API ใน Chrome 113 เพื่อให้สอดคล้องกับกรณีการใช้งานหลัก อย่างไรก็ตาม ยังมีโปรเจ็กต์สำคัญอีกโปรเจ็กต์หนึ่งที่พัฒนาควบคู่ไปด้วย นั่นก็คือ webgpu.h C API ไฟล์ส่วนหัว C นี้จะแสดงรายการขั้นตอนและโครงสร้างข้อมูลที่พร้อมใช้งานทั้งหมดของ WebGPU โดยทำหน้าที่เป็นเลเยอร์ Abstraction ของฮาร์ดแวร์ที่ไม่จำเป็นต้องยึดติดกับแพลตฟอร์ม คุณจึงสร้างแอปพลิเคชันเฉพาะแพลตฟอร์มด้วยอินเทอร์เฟซที่สอดคล้องกันในแพลตฟอร์มต่างๆ
ในเอกสารนี้ คุณจะได้เรียนรู้วิธีเขียนแอป C++ ขนาดเล็กโดยใช้ WebGPU ที่ทำงานทั้งบนเว็บและเฉพาะแพลตฟอร์ม โปรดทราบว่าคุณจะเห็นรูปสามเหลี่ยมสีแดงเดียวกันนี้ปรากฏในหน้าต่างเบราว์เซอร์และหน้าต่างเดสก์ท็อปโดยมีการปรับโค้ดฐานเพียงเล็กน้อย
หลักการทำงาน
หากต้องการดูแอปพลิเคชันที่เสร็จสมบูรณ์ โปรดไปที่ที่เก็บแอปข้ามแพลตฟอร์ม WebGPU
แอปนี้เป็นตัวอย่าง C++ ที่เรียบง่ายซึ่งแสดงวิธีใช้ WebGPU เพื่อสร้างเดสก์ท็อปและแอปเว็บจากฐานโค้ดเดียว เบื้องหลัง จะใช้ webgpu.h ของ WebGPU เป็นเลเยอร์การแยกแยะฮาร์ดแวร์ที่ไม่ขึ้นอยู่กับแพลตฟอร์มผ่าน Wrapper ของ C++ ที่ชื่อ webgpu_cpp.h
บนเว็บ แอปจะสร้างขึ้นโดยใช้ Emscripten ซึ่งมีการเชื่อมโยงที่ใช้ webgpu.h บน JavaScript API ในบางแพลตฟอร์ม เช่น macOS หรือ Windows โปรเจ็กต์นี้สามารถสร้างขึ้นโดยใช้ Dawn ซึ่งเป็นการใช้ WebGPU ข้ามแพลตฟอร์มของ Chromium โปรดทราบว่ามี wgpu-native ซึ่งเป็นการใช้งาน webgpu.h ใน Rust ด้วย แต่ไม่ได้ใช้ในเอกสารนี้
เริ่มต้นใช้งาน
ในการเริ่มต้น คุณต้องมีคอมไพเลอร์ C++ และ CMake เพื่อจัดการการสร้างข้ามแพลตฟอร์มในลักษณะมาตรฐาน สร้างไฟล์ต้นฉบับ main.cpp
และไฟล์บิลด์ CMakeLists.txt
ในโฟลเดอร์เฉพาะ
ไฟล์ main.cpp
ควรมีฟังก์ชัน main()
ที่ว่างเปล่าในตอนนี้
int main() {}
ไฟล์ CMakeLists.txt
มีข้อมูลพื้นฐานเกี่ยวกับโครงการ บรรทัดสุดท้ายระบุชื่อที่เรียกใช้ได้คือ "app" และซอร์สโค้ดคือ main.cpp
cmake_minimum_required(VERSION 3.13) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
เรียกใช้ cmake -B build
เพื่อสร้างไฟล์บิลด์ในโฟลเดอร์ย่อย "build/" และ cmake --build build
เพื่อสร้างแอปและสร้างไฟล์สั่งการได้
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
แอปทำงานได้แต่ยังไม่มีเอาต์พุต เนื่องจากคุณต้องมีวิธีวาดสิ่งต่างๆ บนหน้าจอ
Get Dawn
หากต้องการวาดรูปสามเหลี่ยม คุณสามารถใช้ Dawn ซึ่งเป็นการใช้งาน WebGPU แบบข้ามแพลตฟอร์มของ Chromium ซึ่งรวมถึงไลบรารี C++ ของ GLFW สำหรับการวาดภาพบนหน้าจอ วิธีดาวน์โหลด Dawn คือการเพิ่มเป็น git submodule ลงในที่เก็บข้อมูล คำสั่งต่อไปนี้จะดึงข้อมูลในโฟลเดอร์ย่อย "dawn/"
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
จากนั้นเพิ่มข้อมูลต่อท้ายไฟล์ CMakeLists.txt
ดังนี้
- ตัวเลือก CMake
DAWN_FETCH_DEPENDENCIES
จะดึงข้อมูล Dependency ทั้งหมดของ Dawn - โฟลเดอร์ย่อย
dawn/
รวมอยู่ในเป้าหมาย - แอปของคุณจะขึ้นอยู่กับเป้าหมาย
dawn::webgpu_dawn
,glfw
และwebgpu_glfw
เพื่อให้คุณใช้งานในไฟล์main.cpp
ได้ในภายหลัง
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
เปิดหน้าต่าง
ตอนนี้ Dawn พร้อมให้บริการแล้ว ให้ใช้ GLFW เพื่อวาดสิ่งต่างๆ บนหน้าจอ ไลบรารีนี้รวมอยู่ใน webgpu_glfw
เพื่อความสะดวก ซึ่งช่วยให้คุณเขียนโค้ดที่จัดการหน้าต่างได้โดยไม่คำนึงถึงแพลตฟอร์ม
หากต้องการเปิดหน้าต่างชื่อ "หน้าต่าง WebGPU" ที่มีความละเอียด 512x512 ให้อัปเดตไฟล์ main.cpp
ดังนี้ โปรดทราบว่าที่นี่ใช้ glfwWindowHint()
เพื่อไม่ขอการเริ่มต้น Graphics API ที่เฉพาะเจาะจง
#include <GLFW/glfw3.h>
const uint32_t kWidth = 512;
const uint32_t kHeight = 512;
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// TODO: Render a triangle using WebGPU.
}
}
int main() {
Start();
}
การสร้างแอปขึ้นมาใหม่และเรียกใช้แอปตามปกติส่งผลให้หน้าต่างว่างเปล่า คุณกําลังทําความคืบหน้า
รับอุปกรณ์ GPU
ใน JavaScript นั้น navigator.gpu
คือจุดแรกเข้าสำหรับการเข้าถึง GPU ใน C++ คุณต้องสร้างตัวแปร wgpu::Instance
ด้วยตนเองเพื่อวัตถุประสงค์เดียวกัน ประกาศ instance
ที่ด้านบนของไฟล์ main.cpp
และเรียกใช้ wgpu::CreateInstance()
ภายใน main()
เพื่ออำนวยความสะดวก
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
การเข้าถึง GPU เป็นแบบไม่พร้อมกันเนื่องจากรูปแบบของ JavaScript API ใน C++ ให้สร้างฟังก์ชันตัวช่วย 2 รายการชื่อ GetAdapter()
และ GetDevice()
ซึ่งแสดงผลฟังก์ชัน Callback ที่มี wgpu::Adapter
และ wgpu::Device
ตามลำดับ
#include <iostream>
…
void GetAdapter(void (*callback)(wgpu::Adapter)) {
instance.RequestAdapter(
nullptr,
[](WGPURequestAdapterStatus status, WGPUAdapter cAdapter,
const char* message, void* userdata) {
if (status != WGPURequestAdapterStatus_Success) {
exit(0);
}
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
reinterpret_cast<void (*)(wgpu::Adapter)>(userdata)(adapter);
}, reinterpret_cast<void*>(callback));
}
void GetDevice(void (*callback)(wgpu::Device)) {
adapter.RequestDevice(
nullptr,
[](WGPURequestDeviceStatus status, WGPUDevice cDevice,
const char* message, void* userdata) {
wgpu::Device device = wgpu::Device::Acquire(cDevice);
device.SetUncapturedErrorCallback(
[](WGPUErrorType type, const char* message, void* userdata) {
std::cout << "Error: " << type << " - message: " << message;
},
nullptr);
reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device);
}, reinterpret_cast<void*>(callback));
}
ประกาศตัวแปร wgpu::Adapter
และ wgpu::Device
2 รายการที่ด้านบนของไฟล์ main.cpp
เพื่อให้เข้าถึงได้ง่ายขึ้น อัปเดตฟังก์ชัน main()
ให้เรียก GetAdapter()
และกำหนดการเรียกกลับผลลัพธ์ของ GetAdapter()
ให้กับ adapter
จากนั้นเรียก GetDevice()
และกำหนดการเรียกกลับผลลัพธ์ของ GetDevice()
ให้กับ device
ก่อนเรียก Start()
wgpu::Adapter adapter;
wgpu::Device device;
…
int main() {
instance = wgpu::CreateInstance();
GetAdapter([](wgpu::Adapter a) {
adapter = a;
GetDevice([](wgpu::Device d) {
device = d;
Start();
});
});
}
วาดสามเหลี่ยม
Swap Chain จะไม่แสดงใน JavaScript API เนื่องจากเบราว์เซอร์จะจัดการให้ ใน C++ คุณต้องสร้างโค้ดด้วยตนเอง โปรดประกาศตัวแปร wgpu::Surface
ที่ด้านบนของไฟล์ main.cpp
อีกครั้งเพื่อความสะดวก หลังจากสร้างหน้าต่าง GLFW ใน Start()
ให้เรียกใช้ฟังก์ชัน wgpu::glfw::CreateSurfaceForWindow()
ที่มีประโยชน์เพื่อสร้าง wgpu::Surface
(คล้ายกับ Canvas ของ HTML) และกำหนดค่าโดยเรียกใช้ฟังก์ชัน ConfigureSurface()
ของตัวช่วยใหม่ใน InitGraphics()
นอกจากนี้ คุณยังต้องเรียกใช้ surface.Present()
เพื่อแสดงพื้นผิวถัดไปในลูป while ด้วย ซึ่งจะไม่มีผลที่มองเห็นได้เนื่องจากยังไม่มีการแสดงผล
#include <webgpu/webgpu_glfw.h>
…
wgpu::Surface surface;
wgpu::TextureFormat format;
void ConfigureSurface() {
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
format = capabilities.formats[0];
wgpu::SurfaceConfiguration config{
.device = device,
.format = format,
.width = kWidth,
.height = kHeight};
surface.Configure(&config);
}
void InitGraphics() {
ConfigureSurface();
}
void Render() {
// TODO: Render a triangle using WebGPU.
}
void Start() {
…
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
InitGraphics();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
}
ตอนนี้เป็นโอกาสที่ดีในการสร้างไปป์ไลน์การแสดงผลด้วยโค้ดด้านล่าง ประกาศตัวแปร wgpu::RenderPipeline
ที่ด้านบนของไฟล์ main.cpp
และเรียกใช้ฟังก์ชันตัวช่วย CreateRenderPipeline()
ใน InitGraphics()
เพื่อให้เข้าถึงได้ง่ายขึ้น
wgpu::RenderPipeline pipeline;
…
const char shaderCode[] = R"(
@vertex fn vertexMain(@builtin(vertex_index) i : u32) ->
@builtin(position) vec4f {
const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1));
return vec4f(pos[i], 0, 1);
}
@fragment fn fragmentMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
)";
void CreateRenderPipeline() {
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode;
wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
.nextInChain = &wgslDesc};
wgpu::ShaderModule shaderModule =
device.CreateShaderModule(&shaderModuleDescriptor);
wgpu::ColorTargetState colorTargetState{.format = format};
wgpu::FragmentState fragmentState{.module = shaderModule,
.targetCount = 1,
.targets = &colorTargetState};
wgpu::RenderPipelineDescriptor descriptor{
.vertex = {.module = shaderModule},
.fragment = &fragmentState};
pipeline = device.CreateRenderPipeline(&descriptor);
}
void InitGraphics() {
…
CreateRenderPipeline();
}
สุดท้าย ให้ส่งคำสั่งการแสดงผลไปยัง GPU ในฟังก์ชัน Render()
ที่เรียกแต่ละเฟรม
void Render() {
wgpu::SurfaceTexture surfaceTexture;
surface.GetCurrentTexture(&surfaceTexture);
wgpu::RenderPassColorAttachment attachment{
.view = surfaceTexture.texture.CreateView(),
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store};
wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
.colorAttachments = &attachment};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
pass.SetPipeline(pipeline);
pass.Draw(3);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
device.GetQueue().Submit(1, &commands);
}
การสร้างแอปอีกครั้งด้วย CMake และการเรียกใช้แอปในขณะนี้ส่งผลให้เกิดรูปสามเหลี่ยมสีแดงที่รอคอยมานานในหน้าต่าง! คุณควรพักสักหน่อยนะ
คอมไพล์ไปยัง WebAssembly
มาดูการเปลี่ยนแปลงขั้นต่ำที่จําเป็นในการปรับโค้ดเบสที่มีอยู่เพื่อวาดสามเหลี่ยมสีแดงนี้ในหน้าต่างเบราว์เซอร์กัน ขอย้ำอีกครั้งว่าแอปนี้สร้างขึ้นโดยใช้ Emscripten ซึ่งเป็นเครื่องมือสำหรับรวบรวมโปรแกรม C/C++ ไปยัง WebAssembly ซึ่งมีการเชื่อมโยงที่ใช้ webgpu.h ร่วมกับ JavaScript API
อัปเดตการตั้งค่า CMake
เมื่อติดตั้ง Emscripten แล้ว ให้อัปเดตไฟล์บิลด์ CMakeLists.txt
ดังนี้
สิ่งเดียวที่คุณต้องเปลี่ยนแปลงคือโค้ดที่ไฮไลต์
set_target_properties
ใช้เพื่อเพิ่มนามสกุลไฟล์ "html" ลงในไฟล์เป้าหมายโดยอัตโนมัติ กล่าวคือ คุณจะต้องสร้างไฟล์ "app.html"- ต้องมีตัวเลือก App Link
USE_WEBGPU
เพื่อเปิดใช้การรองรับ WebGPU ใน Emscripten หากไม่มี ไฟล์main.cpp
ของคุณจะเข้าถึงไฟล์webgpu/webgpu_cpp.h
ไม่ได้ - นอกจากนี้ คุณยังต้องใช้ตัวเลือก App Link
USE_GLFW
ที่นี่ด้วยเพื่อให้ใช้โค้ด GLFW ซ้ำได้
cmake_minimum_required(VERSION 3.13) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
if(EMSCRIPTEN)
set_target_properties(app PROPERTIES SUFFIX ".html")
target_link_options(app PRIVATE "-sUSE_WEBGPU=1" "-sUSE_GLFW=3")
else()
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
endif()
อัปเดตโค้ด
ใน Emscripten การสร้าง wgpu::surface
ต้องใช้องค์ประกอบ HTML Canvas ในการดําเนินการนี้ ให้เรียกใช้ instance.CreateSurface()
และระบุตัวเลือก #canvas
เพื่อจับคู่กับองค์ประกอบ Canvas ของ HTML ที่เหมาะสมในหน้า HTML ที่ Emscripten สร้างขึ้น
แทนที่จะใช้ลูป while ให้เรียก emscripten_set_main_loop(Render)
เพื่อให้แน่ใจว่ามีการเรียกใช้ฟังก์ชัน Render()
ในอัตราที่ราบรื่นที่เหมาะสมซึ่งสอดคล้องกับเบราว์เซอร์และจอภาพ
#include <GLFW/glfw3.h>
#include <webgpu/webgpu_cpp.h>
#include <iostream>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#else
#include <webgpu/webgpu_glfw.h>
#endif
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
#if defined(__EMSCRIPTEN__)
wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
canvasDesc.selector = "#canvas";
wgpu::SurfaceDescriptor surfaceDesc{.nextInChain = &canvasDesc};
surface = instance.CreateSurface(&surfaceDesc);
#else
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
#endif
InitGraphics();
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop(Render, 0, false);
#else
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
#endif
}
สร้างแอปด้วย Emscripten
การเปลี่ยนแปลงเพียงอย่างเดียวในการสร้างแอปด้วย Emscripten คือการเพิ่มสคริปต์เชลล์ emcmake
วิเศษไว้หน้าคำสั่ง cmake
ครั้งนี้ให้สร้างแอปในโฟลเดอร์ย่อย build-web
และเริ่มเซิร์ฟเวอร์ HTTP ประการสุดท้าย ให้เปิดเบราว์เซอร์แล้วไปที่ build-web/app.html
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server
ขั้นตอนถัดไป
สิ่งที่จะเกิดขึ้นในอนาคตมีดังนี้
- การปรับปรุงความเสถียรของ API webgpu.h และ webgpu_cpp.h
- การรองรับ Dawn เบื้องต้นสำหรับ Android และ iOS
ในระหว่างนี้ โปรดแจ้งปัญหา WebGPU สำหรับ Emscripten และปัญหา Dawn พร้อมคำแนะนำและคำถาม
แหล่งข้อมูล
คุณสามารถสำรวจซอร์สโค้ดของแอปนี้
หากต้องการเจาะลึกการสร้างแอปพลิเคชัน 3 มิติแบบเนทีฟใน C++ ตั้งแต่ต้นด้วย WebGPU โปรดดูดูเอกสารประกอบ WebGPU สําหรับ C++ และตัวอย่าง WebGPU ของ Dawn แบบเนทีฟ
หากสนใจ Rust คุณยังสำรวจไลบรารีกราฟิก wgpu ที่อิงตาม WebGPU ได้ด้วย ดูตัวอย่าง hello-triangle
บริการรับรองคำให้การ
บทความนี้ได้รับการตรวจสอบโดย Corentin Wallez, Kai Ninomiya และ Rachel Andrew
รูปภาพโดย Marc-Olivier Jodoin ใน Unsplash