WebGPU, web geliştiricileri için birleştirilmiş ve hızlı bir şekilde sunan bir web grafiği API'sıdır daha fazla bilgi edineceksiniz. WebGPU, modern donanım özelliklerini sunar ve oluşturmaya olanak tanır. ve hesaplama işlemleri, Direct3D 12, Metal ve Vulkan'a benzer.
Doğru olsa da bu hikaye eksik. WebGPU, ortak bir çalışmanın sonucudur Microsoft ve Mozilla gibi Apple, Google, Intel ve Mozilla gibi Microsoft. Bunlardan bazıları farklarına WebGPU'nun bir JavaScript API'sinden daha fazlası olduğunu, ancak aynı zamanda Web dışındaki ekosistemlerdeki geliştiricilere yönelik API.
Birincil kullanım alanını karşılamak için JavaScript API, kullanıma sunuldu. Ancak önemli bir diğer unsur birlikte geliştirildiğini göreceksiniz: webgpu.h C API'ye gidin. Bu C başlığı dosyası, kullanılabilen tüm prosedürleri ve veri yapılarını listeler örneğidir. Platformdan bağımsız bir donanım soyutlama katmanı görevi görür. Böylece, her kullanıcı için tutarlı bir arayüz sağlayarak platforma özgü uygulamalar yardımcı olabilir.
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. Kodunuzda, bir tarayıcı penceresinde ve masaüstü penceresinde görünen kırmızı üçgenin aynısını, kod tabanınızda çok az ayarlamayla 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. Temelde, 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 öğesini kullanır.
Web'de uygulama, JavaScript API'nin üzerinde webgpu.h uygulayan bağlamalara sahip Emscripten'e dayalı olarak derlenmiştir. macOS veya Windows gibi belirli platformlarda bu proje, Chromium'un platformlar arası WebGPU uygulaması olan Dawn'a göre geliştirilebilir. Webgpu.h'nin Rust uygulaması olan wgpu-native'nin de bu dokümanda kullanılmadığını belirtmekte fayda var.
Başlayın
Öncelikle, 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ı.
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" olduğunu belirtir ve kaynak kodu 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")
"build/" komutunu kullanarak derleme dosyaları oluşturmak için cmake -B build
komutunu çalıştırın alt klasör ve cmake --build build
öğesini eklemeniz gerekir.
# 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.
Şafak vakti
Üçgeninizi çizmek için Chromium'un platformlar arası WebGPU uygulaması olan Dawn'dan yararlanabilirsiniz. Buna, ekranda çizim yapmak için GLFW C++ kitaplığı da dahildir. Dawn'ı indirmenin bir yolu da deponuza git alt modülü olarak eklemektir. Aşağıdaki komutlar sorguyu "dawn/" içinde getirir alt klasöre koyabilirsiniz.
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
Ardından, CMakeLists.txt
dosyasına şu şekilde ekleme yapın:
- CMake
DAWN_FETCH_DEPENDENCIES
seçeneği, tüm şafak bağımlılıklarını getirir. dawn/
alt klasörü hedefe dahil edilir.- Uygulamanız
dawn::webgpu_dawn
,glfw
vewebgpu_glfw
hedeflerini temel alacak. Böylece bu hedefleri daha sonramain.cpp
dosyasında kullanabileceksiniz.
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
Pencere aç
Şafak artık hazır olduğuna göre ekranda bir şeyler çizmek için GLFW'yu kullanabilirsiniz. Kolaylık olması için webgpu_glfw
ürününde bulunan bu kitaplık, pencere yönetimi için platformdan bağımsız bir kod yazmanıza olanak tanır.
"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 bulunmak için glfwWindowHint()
öğesinin burada 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();
}
Uygulamanın yeniden derlenmesi ve eskisi gibi çalıştırılması boş bir pencereyle sonuçlanıyor. İlerleme kaydediyorsunuz.
GPU cihazı al
JavaScript'te navigator.gpu
, GPU'ya erişmek 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ğlaması açısından, main.cpp
dosyasının üst kısmında instance
değerini beyan edin ve main()
içinde wgpu::CreateInstance()
yöntemini çağırın.
…
#include <webgpu/webgpu_cpp.h>
wgpu::Instance instance;
…
int main() {
instance = wgpu::CreateInstance();
Start();
}
GPU'ya erişim, JavaScript API'nin şeklinden dolayı eşzamansız olarak yapılı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 üst kısmında wgpu::Adapter
ve wgpu::Device
değişkenlerini tanımlayın. main()
işlevini GetAdapter()
yöntemini çağıracak ve sonuç geri çağırmasını adapter
öğesine atayacak şekilde güncelleyin, ardından GetDevice()
yöntemini ç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şim zinciri, JavaScript API'de sunulmaz. Çünkü tarayıcı bu konuyla ilgilenir. 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()
ürününde GLFW penceresini oluşturduktan hemen sonra, bir wgpu::Surface
(HTML tuvaline 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 bu işlevi yapılandırın. Ayrıca, süre döngüsünde bir sonraki dokuyu sunmak için surface.Present()
öğesini de çağırmanız gerekir. Henüz oluşturma işlemi yapılmadığından 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 kodla 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 üst kısmında bir wgpu::RenderPipeline
değişkeni tanımlayın ve InitGraphics()
içindeki yardımcı işlevi CreateRenderPipeline()
olarak ç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 olarak adlandırılan Render()
işlevindeki oluşturma komutlarını GPU'ya 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 vermeyi hak ettiniz.
WebAssembly ile derle
Şimdi, bu kırmızı üçgeni tarayıcı penceresinde çizmek üzere mevcut kod tabanınızı ayarlamak için gereken minimum değişikliklere bakalım. Tekrarlamak gerekirse, uygulama, C/C++ programlarını WebAssembly'de derlemeye yarayan Emscripten'e dayalı olarak geliştirilmiştir. Bu araç, JavaScript API'nin üzerinde webgpu.h'yi uygulayan bağlamalara sahiptir.
CMake ayarlarını güncelle
Emscripten yüklendikten sonra CMakeLists.txt
derleme dosyasını aşağıdaki gibi güncelleyin.
Vurgulanan kod, değiştirmeniz gereken tek şeydir.
set_target_properties
, "html"yi otomatik olarak eklemek için kullanılır dosya uzantısını hedef dosyaya ekleyin. Diğer bir deyişle, bir "app.html" dosyası olarak kaydedebilirsiniz.- Emscripten'de WebGPU desteğini etkinleştirmek için
USE_WEBGPU
uygulama bağlantısı seçeneği gerekir. Aksi haldemain.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 bir HTML tuval öğ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.
süre döngüsü kullanmak yerine, Render()
işlevinin tarayıcı ve monitörle düzgün bir şekilde eşleşen düzgün bir hızda çağrıldığından emin olmak için 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 geliştirme
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çı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 sizi neler bekliyor?
- webgpu.h ve webgpu_cpp.h API'lerinin sabitlemesiyle ilgili iyileştirmeler.
- Android ve iOS için şafak ilk desteği.
Bu süre içinde lütfen Emscripten için WebGPU sorunlarını ve Dawn sorunları ile ilgili önerileri ve soruları 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 isterseniz C++ için WebGPU öğrenme belgelerine ve Dawn Yerel WebGPU Örneklerine göz atın.
Rust ile ilgileniyorsanız WebGPU'ya 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 incelenmiştir.
Fotoğraf: Marc-Olivier Jodoin tarafından Unsplash'te).