Untuk developer web, WebGPU adalah API grafis web yang memberikan akses ke GPU. WebGPU mengekspos kemampuan hardware modern dan memungkinkan rendering dan operasi komputasi pada GPU, mirip dengan Direct3D 12, Metal, dan Vulkan.
Meskipun benar, cerita itu tidak lengkap. WebGPU adalah hasil dari termasuk perusahaan besar, seperti Apple, Google, Intel, Mozilla, dan Microsoft. Di antara mereka, beberapa merealisasikan bahwa WebGPU bisa lebih dari sekadar JavaScript API, tetapi merupakan grafik lintas platform. API untuk developer di berbagai ekosistem, selain web.
Untuk memenuhi kasus penggunaan utama, JavaScript API adalah diperkenalkan di Chrome 113. Namun, alasan penting lain telah dikembangkan bersamanya: webgpu.h C Compute Engine API. File {i>header <i}C ini mencantumkan semua prosedur dan struktur data yang tersedia tentang WebGPU. Enkripsi ini berfungsi sebagai lapisan abstraksi perangkat keras yang tidak bergantung pada platform, yang memungkinkan untuk membangun aplikasi khusus platform dengan menyediakan antarmuka yang konsisten di berbagai platform.
Dalam dokumen ini, Anda akan mempelajari cara menulis aplikasi C++ kecil menggunakan WebGPU yang berjalan di web dan platform tertentu. Peringatan {i>spoiler<i}, Anda akan mendapatkan segitiga merah yang sama dengan yang muncul di jendela browser dan jendela desktop dengan penyesuaian minimal pada codebase Anda.
Bagaimana cara kerjanya?
Untuk melihat aplikasi yang telah selesai, lihat repositori aplikasi lintas platform WebGPU.
Aplikasi ini adalah contoh C++ minimalis yang menunjukkan cara menggunakan WebGPU untuk membangun aplikasi desktop dan web dari satu codebase. Di balik layar, class ini menggunakan webgpu.h WebGPU sebagai lapisan abstraksi hardware agnostik platform melalui wrapper C++ yang disebut webgpu_cpp.h.
Di web, aplikasi ini dibuat berdasarkan Emscripten, yang memiliki binding yang menerapkan webgpu.h di atas JavaScript API. Pada platform tertentu seperti macOS atau Windows, project ini dapat dibangun dengan Dawn, implementasi WebGPU lintas platform Chromium. Perlu diketahui bahwa wgpu-native, implementasi Rust webgpu.h, juga ada, tetapi tidak digunakan dalam dokumen ini.
Mulai
Untuk memulai, Anda memerlukan compiler C++ dan CMake untuk menangani build lintas platform dengan cara standar. Di dalam folder khusus, buat
File sumber main.cpp
dan file build CMakeLists.txt
.
Untuk saat ini, file main.cpp
seharusnya berisi fungsi main()
kosong.
int main() {}
File CMakeLists.txt
berisi informasi dasar tentang project. Baris terakhir mencantumkan nama yang dapat dieksekusi adalah "app" dan kode sumbernya adalah 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")
Jalankan cmake -B build
untuk membuat file build di "build/" sub folder dan cmake --build build
untuk benar-benar membangun aplikasi dan menghasilkan file yang dapat dieksekusi.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
Aplikasi berjalan tetapi belum ada output, karena Anda memerlukan cara untuk menggambar sesuatu di layar.
Dapatkan Fajar
Untuk menggambar segitiga, Anda dapat memanfaatkan Dawn, implementasi WebGPU lintas platform Chromium. Ini mencakup library C++ GLFW untuk menggambar ke layar. Salah satu cara untuk mendownload Dawn adalah dengan menambahkannya sebagai submodul git ke repositori Anda. Perintah berikut mengambilnya di "dawn/" sub folder.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Kemudian, tambahkan ke file CMakeLists.txt
seperti berikut:
- Opsi
DAWN_FETCH_DEPENDENCIES
CMake mengambil semua dependensi Dawn. - Subfolder
dawn/
disertakan dalam target. - Aplikasi Anda akan bergantung pada target
dawn::webgpu_dawn
,glfw
, danwebgpu_glfw
sehingga Anda dapat menggunakannya dalam filemain.cpp
nanti.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
Membuka jendela
Setelah Dawn tersedia, gunakan GLFW untuk menggambar objek di layar. Library ini disertakan dalam webgpu_glfw
untuk memudahkan, memungkinkan Anda menulis kode yang tidak bergantung pada platform untuk pengelolaan jendela.
Untuk membuka jendela bernama "jendela WebGPU" dengan resolusi 512x512, update file main.cpp
seperti di bawah ini. Perlu diperhatikan bahwa glfwWindowHint()
digunakan di sini untuk tidak meminta inisialisasi API grafis tertentu.
#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();
}
Membangun ulang aplikasi dan menjalankannya seperti sebelumnya sekarang menghasilkan jendela kosong. Kemajuan Anda bagus!
Mendapatkan perangkat GPU
Di JavaScript, navigator.gpu
adalah titik entri Anda untuk mengakses GPU. Di C++, Anda harus membuat variabel wgpu::Instance
yang digunakan untuk tujuan yang sama secara manual. Untuk memudahkan, deklarasikan instance
di bagian atas file main.cpp
dan panggil wgpu::CreateInstance()
di dalam main()
.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
Akses GPU bersifat asinkron karena bentuk JavaScript API. Di C++, buat dua fungsi bantuan bernama GetAdapter()
dan GetDevice()
yang masing-masing menampilkan fungsi callback dengan wgpu::Adapter
dan 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));
}
Untuk memudahkan akses, deklarasikan dua variabel wgpu::Adapter
dan wgpu::Device
di bagian atas file main.cpp
. Update fungsi main()
untuk memanggil GetAdapter()
dan menetapkan callback hasilnya ke adapter
, lalu memanggil GetDevice()
dan menetapkan callback hasilnya ke device
sebelum memanggil 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();
});
});
}
Gambar segitiga
Rantai swap tidak ditampilkan di JavaScript API karena browser akan menanganinya. Di C++, Anda perlu membuatnya secara manual. Sekali lagi, untuk memudahkan, deklarasikan variabel wgpu::Surface
di bagian atas file main.cpp
. Tepat setelah membuat jendela GLFW di Start()
, panggil fungsi wgpu::glfw::CreateSurfaceForWindow()
yang praktis untuk membuat wgpu::Surface
(mirip dengan kanvas HTML) dan konfigurasikan dengan memanggil fungsi ConfigureSurface()
helper baru di InitGraphics()
. Anda juga harus memanggil surface.Present()
untuk menampilkan tekstur berikutnya di loop sementara. Hal ini tidak memiliki efek yang terlihat karena belum ada rendering yang terjadi.
#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();
}
}
Sekarang adalah waktu yang tepat untuk membuat pipeline render dengan kode di bawah ini. Untuk memudahkan akses, deklarasikan variabel wgpu::RenderPipeline
di bagian atas file main.cpp
dan panggil fungsi bantuan CreateRenderPipeline()
di 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();
}
Terakhir, kirim perintah rendering ke GPU dalam fungsi Render()
yang disebut setiap frame.
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);
}
Membangun ulang aplikasi dengan CMake dan menjalankannya sekarang menghasilkan segitiga merah yang sudah lama dinantikan di jendela. Istirahatlah—Anda pantas mendapatkannya.
Kompilasi ke WebAssembly
Sekarang mari kita lihat perubahan minimal yang diperlukan untuk menyesuaikan codebase yang ada untuk menggambar segitiga merah ini di jendela browser. Sekali lagi, aplikasi ini dibuat dengan Emscripten, yakni alat untuk mengompilasi program C/C++ ke WebAssembly, yang memiliki binding yang menerapkan webgpu.h di atas JavaScript API.
Memperbarui setelan CMake
Setelah Emscripten diinstal, update file build CMakeLists.txt
seperti berikut.
Kode yang ditandai adalah satu-satunya hal yang perlu Anda ubah.
set_target_properties
digunakan untuk menambahkan "html" secara otomatis ekstensi file ke file target. Dengan kata lain, Anda akan menghasilkan "app.html" .- Opsi link aplikasi
USE_WEBGPU
diperlukan untuk mengaktifkan dukungan WebGPU di Emscripten. Tanpa file tersebut, filemain.cpp
Anda tidak dapat mengakses filewebgpu/webgpu_cpp.h
. - Opsi link aplikasi
USE_GLFW
juga diperlukan di sini agar Anda dapat menggunakan kembali kode 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()
Mengupdate kode
Di Emscripten, pembuatan wgpu::surface
memerlukan elemen kanvas HTML. Untuk itu, panggil instance.CreateSurface()
dan tentukan pemilih #canvas
untuk mencocokkan elemen kanvas HTML yang sesuai di halaman HTML yang dibuat oleh Emscripten.
Daripada menggunakan loop sementara, panggil emscripten_set_main_loop(Render)
untuk memastikan fungsi Render()
dipanggil pada kecepatan yang tepat dan selaras dengan browser dan monitor.
#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
}
Membangun aplikasi dengan Emscripten
Satu-satunya perubahan yang diperlukan untuk membangun aplikasi dengan Emscripten adalah dengan menambahkan perintah cmake
dengan skrip shell emcmake
ajaib. Kali ini, buat aplikasi di subfolder build-web
dan mulai server HTTP. Terakhir, buka browser Anda dan kunjungi 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
Langkah berikutnya
Berikut hal yang dapat Anda harapkan pada masa mendatang:
- Peningkatan stabilisasi API webgpu.h dan webgpu_cpp.h.
- Dukungan awal fajar untuk Android dan iOS.
Sementara itu, ajukan masalah WebGPU untuk Emscripten dan Masalah Dawn beserta saran dan pertanyaan.
Resource
Jangan ragu untuk mempelajari kode sumber aplikasi ini.
Jika Anda ingin mempelajari lebih lanjut cara membuat aplikasi 3D native dalam C++ dari awal dengan WebGPU, lihat Mempelajari WebGPU untuk dokumentasi C++ dan Contoh WebGPU Dawn Native.
Jika tertarik dengan Rust, Anda juga dapat menjelajahi library grafis wgpu berdasarkan WebGPU. Lihat demo hello-triangle.
Ucapan terima kasih
Artikel ini diulas oleh Corentin Wallez, Kai Ninomiya, dan Rachel Andrew.
Foto oleh Marc-Olivier Jodoin di Unsplash.