Web geliştiricileri için WebGPU, GPU'lara birleşik ve hızlı erişim sağlayan bir web grafiği API'sidir. WebGPU, modern donanım özelliklerini sunar ve Direct3D 12, Metal ve Vulkan'a benzer şekilde, bir GPU üzerinde oluşturma ve hesaplama işlemlerine olanak tanır.
Doğru olsa da bu 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 kullanıma sunuldu. 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 hizmet eder. Böylece, farklı platformlarda tutarlı bir arayüz sağlayarak platforma özel uygulamalar oluşturabilirsiniz.
Bu dokümanda, hem web'de hem de belirli platformlarda çalışan WebGPU'yu kullanarak 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örde 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")
Derleme dosyalarını "build/" alt klasöründe oluşturmak için cmake -B build
komutunu, uygulamayı derleyip yürütülebilir dosyayı oluşturmak için de cmake --build build
komutunu çalıştırın.
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
Uygulama çalışıyor ancak ekranda bir şeyler çizmek için bir yönteme ihtiyacınız olduğundan henüz çıktı yok.
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ı bir "dawn/" alt klasörüne 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++'ta, sırasıyla wgpu::Adapter
ve wgpu::Device
içeren bir geri çağırma işlevi döndüren GetAdapter()
ve GetDevice()
adında 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 bu dosyayı manuel olarak oluşturmanız gerekir. Bir kez daha kolaylık sağlamak için main.cpp
dosyasının üst kısmında bir wgpu::Surface
değişkeni tanımlayın. Start()
içinde GLFW penceresini oluşturduktan hemen sonra, wgpu::Surface
(HTML kanvasına benzer) oluşturmak için kullanışlı wgpu::glfw::CreateSurfaceForWindow()
işlevini çağırın 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 olmadığı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ırmak artık bir pencerede uzun süredir beklenen kırmızı üçgeni gösteriyor! 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üncelle
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()
yöntemini çağırın ve Emscripten tarafından oluşturulan HTML sayfasındaki uygun HTML tuval öğesiyle eşleştirmek üzere #canvas
seçiciyi belirtin.
Render()
işlevinin tarayıcı ve monitörle düzgün şekilde uyumlu olacak şekilde uygun bir akıcılıkta ç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 gereken tek 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 C++'ta sıfırdan yerel 3D uygulamalar oluşturma konusunda daha fazla bilgi edinmek isterseniz C++ için WebGPU hakkında bilgi edinme ve Dawn Yerel WebGPU Örnekleri sayfalarına göz atın.
Rust'a ilgi duyuyorsanız WebGPU'ye dayalı 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.