สำหรับนักพัฒนาเว็บ 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 API ไฟล์ส่วนหัว C นี้จะแสดงกระบวนการและโครงสร้างข้อมูลทั้งหมดที่พร้อมใช้งาน ของ WebGPU โดยทำหน้าที่เป็นชั้นแอบสแตรกต์ของฮาร์ดแวร์ที่เข้าใจได้โดยไม่จำเป็นต้องเข้าใจแพลตฟอร์ม ซึ่งช่วยให้ คุณสามารถสร้างแอปพลิเคชันเฉพาะแพลตฟอร์ม ด้วยอินเทอร์เฟซที่สอดคล้องกัน ในแพลตฟอร์มต่างๆ
ในเอกสารนี้ คุณจะได้เรียนรู้วิธีเขียนแอป C++ ขนาดเล็กโดยใช้ WebGPU ที่ทำงานทั้งบนเว็บและเฉพาะแพลตฟอร์ม ระวังสปอยล์ คุณจะเห็นสามเหลี่ยมสีแดงรูปเดียวกันที่ปรากฏในหน้าต่างเบราว์เซอร์และหน้าต่างเดสก์ท็อปโดยมีการปรับเปลี่ยนฐานของโค้ดน้อยที่สุด
หลักการทำงาน
หากต้องการดูแอปพลิเคชันที่สมบูรณ์ โปรดดูที่เก็บแอปแบบข้ามแพลตฟอร์มของ WebGPU
แอปเป็นตัวอย่าง C++ ที่เรียบง่ายซึ่งแสดงวิธีใช้ WebGPU เพื่อสร้างแอปบนเดสก์ท็อปและเว็บแอปจากฐานของโค้ดเดียว ในการทำงานเบื้องหลัง มีการใช้ webgpu.h ของ WebGPU เป็นเลเยอร์ Abstraction ของฮาร์ดแวร์ที่ไม่จำเป็นต้องใช้ร่วมกับแพลตฟอร์มผ่าน Wrapper C++ ที่ชื่อ webgpu_cpp.h
สำหรับบนเว็บ แอปจะสร้างขึ้นโดยใช้ Emscripten ซึ่งมีการเชื่อมโยงที่ใช้ webgpu.h ร่วมกับ JavaScript API ในแพลตฟอร์มที่เฉพาะเจาะจง เช่น macOS หรือ Windows โปรเจ็กต์นี้สามารถสร้างขึ้นโดยใช้ Dawn ซึ่งเป็นการใช้ WebGPU ข้ามแพลตฟอร์มของ Chromium สิ่งที่ควรทราบคือ wgpu-native ซึ่งเป็นการใช้งาน Rust ของ webgpu.h นั้นมีแต่ไม่ได้ใช้ในเอกสารนี้
เริ่มต้นใช้งาน
ในการเริ่มต้น คุณต้องมีคอมไพเลอร์ 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
แอปทำงานแต่ไม่มีเอาต์พุตเนื่องจากคุณต้องมีวิธีวาดสิ่งต่างๆ บนหน้าจอ
รับรุ่งอรุณ
ในการวาดรูปสามเหลี่ยม คุณสามารถใช้ประโยชน์จาก Dawn ซึ่งเป็นการใช้ WebGPU ข้ามแพลตฟอร์มของ Chromium ซึ่งรวมถึงไลบรารี C++ GLFW สำหรับการวาดภาพไปยังหน้าจอ วิธีหนึ่งในการดาวน์โหลด Dawn คือการเพิ่มโมดูลนี้เป็นโมดูลย่อยของ git ลงในที่เก็บ คำสั่งต่อไปนี้ดึงข้อมูลใน "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()
ที่นี่เพื่อขอไม่มีการเริ่มต้น 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));
}
เพื่อการเข้าถึงที่ง่ายขึ้น ให้ประกาศตัวแปร 2 รายการ wgpu::Adapter
และ wgpu::Device
ที่ด้านบนของไฟล์ main.cpp
อัปเดตฟังก์ชัน main()
เพื่อเรียก GetAdapter()
และกำหนดการเรียกผลลัพธ์ให้กับ adapter
จากนั้นเรียก 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();
});
});
}
วาดสามเหลี่ยม
การสลับเชนจะไม่แสดงใน JavaScript API เนื่องจากเบราว์เซอร์มีหน้าที่จัดการ ใน C++ คุณต้องสร้างโค้ดด้วยตนเอง โปรดประกาศตัวแปร wgpu::Surface
ที่ด้านบนของไฟล์ main.cpp
อีกครั้งเพื่อความสะดวก หลังจากสร้างหน้าต่าง GLFW ใน Start()
แล้ว ให้เรียกใช้ฟังก์ชัน wgpu::glfw::CreateSurfaceForWindow()
ที่มีประโยชน์เพื่อสร้าง wgpu::Surface
(คล้ายกับ Canvas ของ HTML) และกำหนดค่าโดยเรียกใช้ฟังก์ชัน ConfigureSurface()
ของตัวช่วยใหม่ใน InitGraphics()
คุณยังต้องเรียกใช้ surface.Present()
เพื่อแสดงพื้นผิวถัดไปในลูป วิธีนี้ยังไม่มีเอฟเฟกต์ที่มองเห็นได้เนื่องจากยังไม่มีการแสดงภาพ
#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" - ต้องมีตัวเลือกลิงก์แอป
USE_WEBGPU
เพื่อเปิดใช้การรองรับ WebGPU ใน Emscripten หากไม่มี ไฟล์main.cpp
ของคุณจะเข้าถึงไฟล์webgpu/webgpu_cpp.h
ไม่ได้ - คุณยังต้องใช้ตัวเลือกลิงก์แอป
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
ต้องใช้องค์ประกอบ Canvas แบบ HTML สำหรับการดำเนินการนี้ ให้เรียกใช้ instance.CreateSurface()
และระบุตัวเลือก #canvas
เพื่อจับคู่องค์ประกอบ Canvas ของ HTML ที่เหมาะสมในหน้า HTML ที่สร้างโดย Emscripten
แทนที่จะใช้การวนซ้ำ ให้เรียกใช้ 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 คือการเพิ่มคำสั่ง cmake
ด้วยสคริปต์ Shell เวทมนตร์ emcmake
ครั้งนี้ให้สร้างแอปในโฟลเดอร์ย่อย 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 Native
หากสนใจเกี่ยวกับ Rust คุณลองสำรวจไลบรารีกราฟิก wgpu ที่อิงตาม WebGPU ได้ด้วย ชมการสาธิตสวัสดีสามเหลี่ยมเหล่านี้
บริการรับรองคำให้การ
บทความนี้ได้รับการตรวจสอบโดย Corentin Wallez, Kai Ninomiya และ Rachel Andrew
รูปภาพโดย Marc-Olivier Jodoin ใน Unsplash