Web geliştiricileri için WebGPU, GPU'lara birleşik ve hızlı erişim sağlayan bir web grafik API'sidir. WebGPU, modern donanım özelliklerini gösterir ve Direct3D 12, Metal ve Vulkan'a benzer şekilde GPU'da oluşturma ve hesaplama işlemlerine olanak tanır.
Bu doğru olsa da hikaye eksik. WebGPU, Apple, Google, Intel, Mozilla ve Microsoft gibi büyük şirketlerin dahil olduğu ortak bir çalışmanın sonucudur. Bu geliştiriciler arasında, WebGPU'nun bir JavaScript API'sinden daha fazlası olabileceğini, web dışındaki ekosistemlerdeki geliştiriciler için platformlar arası bir grafik API'si olabileceğini fark edenler de vardı.
Birincil kullanım alanını karşılamak için Chrome 113'te bir JavaScript API'si tanıtıldı. Bununla birlikte, bununla birlikte önemli bir proje daha geliştirildi: webgpu.h C API'si. Bu C başlık dosyası, WebGPU'nun mevcut tüm prosedürlerini ve veri yapılarını listeler. Platformdan bağımsız bir donanım soyutlama katmanı olarak farklı platformlarda tutarlı bir arayüz sağlayarak platforma özel uygulamalar oluşturmanıza olanak tanır.
Bu dokümanda, WebGPU'yu kullanarak hem web'de hem de belirli platformlarda çalışan küçük bir C++ uygulamasının nasıl yazılacağını öğreneceksiniz. Spoiler uyarısı: Kod tabanınızda minimum düzeyde ayarlama yaparak tarayıcı penceresinde ve masaüstü penceresinde görünen aynı kırmızı üçgeni görürsünüz.
İşleyiş şekli
Tamamlanmış uygulamayı görmek için WebGPU platformlar arası uygulama deposuna göz atın.
Uygulama, tek bir kod tabanından masaüstü ve web uygulamaları oluşturmak için WebGPU'nun nasıl kullanılacağını gösteren minimalist bir C++ örneğidir. Arka planda, webgpu_cpp.h adlı bir C++ sarmalayıcısı aracılığıyla platformdan bağımsız bir donanım soyutlama katmanı olarak WebGPU'nin webgpu.h dosyasını kullanır.
Web'de uygulama, JavaScript API'nin üzerine webgpu.h'yi uygulayan bağlantılara sahip Emscripten için geliştirilmiştir. macOS veya Windows gibi belirli platformlarda bu proje, Chromium'un platformlar arası WebGPU uygulaması olan Dawn ile derlenebilir. webgpu.h'nin Rust uygulaması olan wgpu-native'in de mevcut olduğunu ancak bu dokümanda kullanılmadığını belirtmek isteriz.
Başlayın
Başlamak için bir C++ derleyiciye ve platformlar arası derlemeleri standart bir şekilde yönetmek için CMake'e ihtiyacınız vardır. Özel bir klasör içinde bir main.cpp
kaynak dosyası ve bir CMakeLists.txt
derleme dosyası oluşturun.
main.cpp
dosyası şimdilik boş bir main()
işlevi içermelidir.
int main() {}
CMakeLists.txt
dosyası, projeyle ilgili temel bilgileri içerir. Son satırda, yürütülebilir dosyanın adının "uygulama" ve kaynak kodunun main.cpp
olduğu belirtilmektedir.
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")
"build/" alt klasöründe derleme dosyaları oluşturmak için cmake -B build
'ü, uygulamayı derlemek ve yürütülebilir dosyayı oluşturmak için cmake --build build
'ü çalıştırın.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
Uygulama çalışır ancak ekranda bir şeyler çizmek için bir yönteme ihtiyacınız olduğundan henüz çıkış yoktur.
Get Dawn
Üçgeninizi çizmek için Chromium'un platformlar arası WebGPU uygulaması olan Dawn'dan yararlanabilirsiniz. Buna ekrana çizim yapmak için kullanılan GLFW C++ kitaplığı da dahildir. Dawn'ı indirmenin bir yolu, deponuza git alt modülü olarak eklemektir. Aşağıdaki komutlar, dosyayı "dawn/" alt klasöründe getirir.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Ardından, CMakeLists.txt
dosyasına aşağıdaki şekilde ekleyin:
- CMake
DAWN_FETCH_DEPENDENCIES
seçeneği, tüm Dawn bağımlılarını getirir. dawn/
alt klasörü hedefe dahil edilmiştir.- Uygulamanız
dawn::webgpu_dawn
,glfw
vewebgpu_glfw
hedeflerine bağlıdır. Bu sayede bunları daha sonramain.cpp
dosyasında kullanabilirsiniz.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
Pencere açma
Dawn kullanıma sunulduğundan, ekranda çizim yapmak için GLFW'yi kullanın. Kolaylık sağlamak için webgpu_glfw
'e dahil edilen bu kitaplık, pencere yönetimi için platformdan bağımsız kod yazmanıza olanak tanır.
512x512 çözünürlüğe sahip "WebGPU penceresi" adlı bir pencere açmak için main.cpp
dosyasını aşağıdaki gibi güncelleyin. Burada, belirli bir grafik API'sinin başlatılmasını istememek için glfwWindowHint()
değerinin kullanıldığını unutmayın.
#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();
}
Uygulamayı yeniden derleyip önceki gibi çalıştırdığınızda artık boş bir pencere görüyorsunuz. İlerleme kaydediyorsunuz.
GPU cihazı alma
JavaScript'te navigator.gpu
, GPU'ya erişim için giriş noktanızdır. C++'ta, aynı amaç için kullanılan bir wgpu::Instance
değişkenini manuel olarak oluşturmanız gerekir. Kolaylık sağlamak için main.cpp
dosyasının en üstünde instance
'ü tanımlayın ve main()
içinde wgpu::CreateInstance()
'yi çağırın.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
JavaScript API'sinin yapısı nedeniyle GPU'ya erişim ayarsızdır. C++'da, sırasıyla wgpu::Adapter
ve wgpu::Device
içeren bir geri çağırma işlevi döndüren GetAdapter()
ve GetDevice()
adlı iki yardımcı işlev oluşturun.
#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));
}
Daha kolay erişim için main.cpp
dosyasının en üstünde wgpu::Adapter
ve wgpu::Device
adlı iki değişken tanımlayın. main()
işlevini, GetAdapter()
'u çağırıp sonucun geri çağırma işlevini adapter
'ye atayacak, ardından GetDevice()
'ı çağırıp sonucun geri çağırma işlevini device
'e atayacak ve Start()
'i çağıracak şekilde güncelleyin.
wgpu::Adapter adapter;
wgpu::Device device;
…
int main() {
instance = wgpu::CreateInstance();
GetAdapter([](wgpu::Adapter a) {
adapter = a;
GetDevice([](wgpu::Device d) {
device = d;
Start();
});
});
}
Üçgen çizme
Değişim zinciri, tarayıcı tarafından yönetildiği için JavaScript API'sinde gösterilmez. C++'ta bunu manuel olarak oluşturmanız gerekir. Kolaylık sağlamak için main.cpp
dosyasının en üstünde bir wgpu::Surface
değişkeni tanımlayın. Start()
içinde GLFW penceresini oluşturduktan hemen sonra, kullanışlı wgpu::glfw::CreateSurfaceForWindow()
işlevini çağırarak bir wgpu::Surface
(HTML kanvasına benzer) oluşturun ve InitGraphics()
içinde yeni yardımcı ConfigureSurface()
işlevini çağırarak yapılandırın. while döngüsünde bir sonraki dokuyu sunmak için surface.Present()
işlevini de çağırmanız gerekir. Henüz oluşturma işlemi yapılmadığından bu durumun görünür bir etkisi yoktur.
#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();
}
}
Şimdi aşağıdaki kodla oluşturma ardışık düzenini oluşturmanın tam zamanı. Daha kolay erişim için main.cpp
dosyasının en üstünde bir wgpu::RenderPipeline
değişkeni tanımlayın ve InitGraphics()
içinde CreateRenderPipeline()
yardımcı işlevini çağırın.
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();
}
Son olarak, her kare çağrısında Render()
işlevinde GPU'ya oluşturma komutları gönderin.
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);
}
Uygulamayı CMake ile yeniden derleyip çalıştırdığınızda artık bir pencerede uzun zamandır beklenen kırmızı üçgen görünüyor. Biraz ara verin. Bunu hak ettiniz.
WebAssembly olarak derleme
Şimdi, mevcut kod tabanınızı bir tarayıcı penceresinde bu kırmızı üçgeni çizecek şekilde ayarlamak için gereken minimum değişikliklere göz atalım. Uygulama, C/C++ programlarını WebAssembly'e derleyen bir araç olan Emscripten'e göre oluşturulur. Bu araç, JavaScript API'sinin üzerine webgpu.h'yi uygulayan bağlantılara sahiptir.
CMake ayarlarını güncelleme
Emscripten yüklendikten sonra CMakeLists.txt
derleme dosyasını aşağıdaki gibi güncelleyin.
Değiştirmeniz gereken tek şey vurgulanan koddur.
set_target_properties
, hedef dosyaya "html" dosya uzantısını otomatik olarak eklemek için kullanılır. Diğer bir deyişle, "app.html" dosyası oluşturursunuz.- Emscripten'de WebGPU desteğini etkinleştirmek için
USE_WEBGPU
uygulama bağlantısı seçeneği gerekir. Aksi takdirdemain.cpp
dosyanızwebgpu/webgpu_cpp.h
dosyasına erişemez. - GLFW kodunuzu yeniden kullanabilmeniz için burada
USE_GLFW
uygulama bağlantısı seçeneği de gereklidir.
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()
Kodu güncelleme
Emscripten'de wgpu::surface
oluşturmak için HTML kanvas öğesi gerekir. Bunun için instance.CreateSurface()
işlevini çağırın ve Emscripten tarafından oluşturulan HTML sayfasındaki uygun HTML kanvas öğesiyle eşleşecek #canvas
seçiciyi belirtin.
Render()
işlevinin tarayıcı ve monitörle düzgün şekilde uyumlu olacak şekilde uygun bir hızda çağrılmasını sağlamak için while döngüsü yerine emscripten_set_main_loop(Render)
işlevini çağırın.
#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
}
Uygulamayı Emscripten ile derleme
Uygulamayı Emscripten ile derlemek için tek gereken değişiklik, cmake
komutlarının başına sihirli emcmake
kabuk komut dosyasını eklemektir. Bu kez uygulamayı bir build-web
alt klasöründe oluşturun ve bir HTTP sunucusu başlatın. Son olarak, tarayıcınızı açıp build-web/app.html
adresini ziyaret edin.
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server
Sırada ne var?
Gelecekte ne gibi değişiklikler görebileceğiniz aşağıda açıklanmıştır:
- webgpu.h ve webgpu_cpp.h API'lerinin kararlılığında iyileştirmeler yapıldı.
- Android ve iOS için Dawn'ın ilk desteği.
Bu süreçte, Emscripten için WebGPU sorunlarını ve Dawn sorunlarını öneri ve sorularla birlikte gönderin.
Kaynaklar
Bu uygulamanın kaynak kodunu inceleyebilirsiniz.
WebGPU ile sıfırdan C++'ta yerel 3D uygulamalar oluşturma hakkında daha fazla bilgi edinmek istiyorsanız C++ için WebGPU'yi öğrenme dokümanlarına ve Dawn Native WebGPU örneklerine göz atın.
Rust'u kullanmak istiyorsanız WebGPU tabanlı wgpu grafik kitaplığını da keşfedebilirsiniz. hello-triangle demosuna göz atın.
Tasdik
Bu makale Corentin Wallez, Kai Ninomiya ve Rachel Andrew tarafından incelendi.
Fotoğraf: Marc-Olivier Jodoin, Unsplash.