Web geliştiricileri için WebGPU, GPU'lara birleştirilmiş ve hızlı erişim sağlayan bir web grafiği API'sidir. WebGPU, modern donanım özelliklerini ortaya koyar ve Direct3D 12, Metal ve Vulkan'a benzer şekilde bir GPU üzerinde oluşturma ve hesaplama işlemlerine imkan tanır.
Doğru olsa da bu hikaye tam değil. WebGPU; Apple, Google, Intel, Mozilla ve Microsoft gibi büyük şirketlerin aralarında bulunduğu ortak bir çalışmanın sonucudur. Bunların bazıları, WebGPU'nun bir JavaScript API'den daha fazlası olabileceğini, ancak web dışındaki ekosistemlerdeki geliştiriciler için platformlar arası bir grafik API'si olabileceğini anladı.
Birincil kullanım alanını karşılamak için Chrome 113'te bir JavaScript API kullanıma sunulmuştur. Ancak, bununla birlikte bir başka önemli proje de geliştirilmiştir: webgpu.h C API. Bu C başlık dosyası, WebGPU'nun kullanılabilir tüm prosedürlerini ve veri yapılarını listeler. Platformdan bağımsız bir donanım soyutlama katmanı olarak işlev görür ve farklı platformlarda tutarlı bir arayüz sağlayarak platforma özgü uygulamalar oluşturmanıza olanak tanır.
Bu belgede, 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ı, tarayıcı penceresinde ve masaüstü penceresinde kod tabanınızda küçük ayarlamalar yaparak aynı kırmızı üçgeni görürsünüz.
İşleyiş şekli
Tamamlanan uygulamayı görmek için WebGPU platformlar arası uygulaması deposuna göz atın.
Bu 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. Temel olarak, webgpu_cpp.h adlı bir C++ sarmalayıcı aracılığıyla platformdan bağımsız bir donanım soyutlama katmanı olarak WebGPU'nun webgpu.h'sini kullanır.
Web'de uygulama, JavaScript API'sına ek olarak webgpu.h'yi uygulayan bağlamalara sahip Emscripten'e dayalı olarak oluşturulmuştur. macOS veya Windows gibi belirli platformlarda, bu proje Chromium'un platformlar arası WebGPU uygulaması Dawn'a göre derlenebilir. Webgpu.h'nin Rust uygulaması olan wgpu-native'in de mevcut olduğunu ve bu dokümanda kullanılmadığını belirtmek isteriz.
Başlayın
Başlamak için, platformlar arası derlemeleri standart bir şekilde işlemek için bir C++ derleyicisine ve CMake'e ihtiyacınız vardır. Özel bir klasörde main.cpp
kaynak dosyası ve 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ır, yürütülebilir adın "app" ve kaynak kodu main.cpp
olduğunu belirtiyor.
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
, uygulamayı gerçekten derlemek ve yürütülebilir dosyayı oluşturmak içinse 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ğu için henüz herhangi bir çıkış yok.
Gün Doğumu
Üçgeninizi çizmek için Chromium'un platformlar arası WebGPU uygulaması olan Dawn'dan yararlanabilirsiniz. Ekrana çizim için kullanılan GLFW C++ kitaplığı da buna dahildir. Dawn'ı indirmenin bir yolu da onu kod deponuza git alt modülü olarak eklemektir. Aşağıdaki komutlar veri öğesini "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 gibi ekleyin:
- CMake
DAWN_FETCH_DEPENDENCIES
seçeneği tüm Dawn bağımlılıklarını getirir. dawn/
alt klasörü hedefe dahildir.- Uygulamanız
webgpu_cpp
,webgpu_dawn
,glfw
vewebgpu_glfw
hedeflerini temel alacak. Böylece, bunları daha sonramain.cpp
dosyasında kullanabilirsiniz.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_cpp webgpu_dawn glfw webgpu_glfw)
Pencere açma
Artık Dawn kullanılabilir olduğuna göre ekranda çizim yapmak için GLFW'yu kullanın. Kolaylık olması için webgpu_glfw
paketine dahil olan bu kitaplık, pencere yönetimi için platformdan bağımsız kod yazmanıza olanak tanır.
512x512 çözünürlüklü "WebGPU penceresi" adlı bir pencere açmak için main.cpp
dosyasını aşağıdaki gibi güncelleyin. Belirli bir grafik API'si başlatma isteğinde bulunmamak için burada glfwWindowHint()
öğesinin 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 derleme ve şimdi önceki gibi çalıştırması boş bir pencerede açılmasıyla sonuçlanır. İlerleme kaydediyorsunuz.
GPU cihazı al
JavaScript'te, GPU'ya erişmek için navigator.gpu
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 olması açısından, main.cpp
dosyasının en üstünde instance
özelliğini beyan edin ve main()
içinde wgpu::CreateInstance()
çağrısı yapın.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
GPU'ya erişim, JavaScript API'sinin şeklinden dolayı eşzamansı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()
çağrısı yapacak şekilde güncelleyin ve sonuç geri çağırmasını adapter
öğesine atayın, ardından GetDevice()
işlevini çağırın ve Start()
çağrısından önce sonuç geri çağırmasını device
öğesine atayın.
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 çizin
Değiştirme zinciri, tarayıcı ilgilendiği için JavaScript API'de açığa çıkmaz. C++'da bunu manuel olarak oluşturmanız gerekir. main.cpp
dosyasının en üstünde, kolaylık sağlaması için bir kez daha wgpu::Surface
değişkeni bildirin. Start()
ürününde GLFW penceresini oluşturduktan hemen sonra, kullanışlı wgpu::glfw::CreateSurfaceForWindow()
işlevini çağırarak wgpu::Surface
(HTML tuvaline benzer) oluşturun ve InitGraphics()
içinde yeni yardımcı ConfigureSurface()
işlevini çağırarak bu işlevi yapılandırın. Ayrıca, işlem döngüsündeki sonraki dokuyu sunmak için surface.Present()
işlevini çağırmanız gerekir. Henüz oluşturma işlemi yapılmadığı için bunun 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 kodu kullanarak görüntü oluşturma ardışık düzenini oluşturmanın zamanı geldi. 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şlevi ç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 bir kareyi çağıran Render()
işlevindeki 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 uzun zamandır beklenen kırmızı bir üçgenle karşılaşacaksınız. Biraz ara vermeyi hak ettiniz.
WebAssembly ile derleyin
Bu kırmızı üçgeni bir tarayıcı penceresinde çizmek amacıyla mevcut kod tabanınızı ayarlamak için gereken minimum değişikliklere göz atalım. Uygulama yine, C/C++ programlarını WebAssembly'de derleme aracı olan Emscripten'e göre derlenir. Bu araç, JavaScript API'sının üzerine webgpu.h uygulayan bağlamalara sahiptir.
CMake ayarlarını güncelleme
Emscripten yüklendikten sonra CMakeLists.txt
derleme dosyasını aşağıdaki şekilde güncelleyin.
Değiştirmeniz gereken tek şey, vurgulanan koddur.
set_target_properties
, "html" dosya uzantısını hedef dosyaya otomatik olarak eklemek için kullanılır. Diğer bir deyişle, bir "app.html" dosyası oluşturursunuz.- Emscripten'da WebGPU desteğini etkinleştirmek için
USE_WEBGPU
uygulama bağlantısı seçeneği gereklidir. Bu olmadanmain.cpp
dosyanız,webgpu/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 webgpu_cpp webgpu_dawn glfw webgpu_glfw)
endif()
Kodu güncelleyin
Emscripten'da wgpu::surface
oluşturmak için HTML tuval öğesi gerekir. Bunun için instance.CreateSurface()
yöntemini çağırın ve #canvas
seçiciyi, Emscripten tarafından oluşturulan HTML sayfasındaki uygun HTML tuval öğesiyle eşleşecek şekilde belirtin.
Süre döngüsü kullanmak yerine, Render()
işlevinin tarayıcı ve monitörle düzgün bir şekilde hizalanan uygun bir hızda çağrıldığından emin olmak için emscripten_set_main_loop(Render)
yöntemini ç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 oluşturma
Uygulamayı Emscripten ile derlemek için gereken tek değişiklik, cmake
komutlarının başına sihirli emcmake
kabuk komut dosyası eklemektir. Bu kez uygulamayı build-web
alt klasöründe oluşturun ve HTTP sunucusu başlatın. Son olarak, tarayıcınızı açın ve 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 şunları bekleyebilirsiniz:
- webgpu.h ve webgpu_cpp.h API'lerinin kararlı hale getirilmesiyle ilgili iyileştirmeler.
- Android ve iOS için Dawn ilk destek.
Bu süre zarfında lütfen öneri ve sorularınızla Emscripten için WebGPU sorunlarını ve Dawn sorunlarını bildirin.
Kaynaklar
Bu uygulamanın kaynak kodunu inceleyebilirsiniz.
WebGPU ile C++'ta sıfırdan yerel 3D uygulamalar oluşturma konusunda daha fazla bilgi edinmek istiyorsanız, C++ dokümanları için WebGPU'yu Öğrenme ve Dawn Native WebGPU Example sayfalarına göz atın.
Rust ile ilgileniyorsanız WebGPU'ya dayalı wgpu grafik kitaplığını da keşfedebilirsiniz. Geliştiricinin 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 tarafından Unsplash'ta yayınlandı.